Kritim Yantra
Feb 24, 2025
SOLID design principles are a set of guidelines that help developers write clean, maintainable, and scalable code. In this blog, we’ll break down each principle in simple terms and see how to apply them in Laravel with easy examples. Let’s dive in!
A class should have only one reason to change (i.e., do one thing).
Bad Example: A Laravel controller handling validation, business logic, and database operations.
class UserController extends Controller {
public function store(Request $request) {
// Validate
$request->validate(['email' => 'required|email']);
// Business logic
$user = new User();
$user->email = $request->email;
$user->save();
// Send welcome email
Mail::to($user)->send(new WelcomeEmail());
return redirect('/users');
}
}
Good Example: Split responsibilities into dedicated classes.
// Step 1: Create a Form Request
class StoreUserRequest extends FormRequest {
public function rules() {
return ['email' => 'required|email'];
}
}
// Step 2: Create a Service Class
class UserService {
public function createUser(array $data): User {
$user = User::create($data);
Mail::to($user)->send(new WelcomeEmail());
return $user;
}
}
// Step 3: Simplified Controller
class UserController extends Controller {
public function store(StoreUserRequest $request, UserService $service) {
$service->createUser($request->validated());
return redirect('/users');
}
}
Classes should be open for extension but closed for modification.
Bad Example: A payment processor that changes every time a new gateway is added.
class PaymentController extends Controller {
public function pay(Request $request) {
$gateway = $request->gateway;
if ($gateway === 'stripe') {
// Stripe logic
} elseif ($gateway === 'paypal') {
// PayPal logic
}
}
}
Good Example: Use an interface to allow new payment gateways without altering existing code.
interface PaymentGateway {
public function pay(float $amount);
}
class StripeGateway implements PaymentGateway {
public function pay(float $amount) { /* Stripe logic */ }
}
class PayPalGateway implements PaymentGateway {
public function pay(float $amount) { /* PayPal logic */ }
}
class PaymentController extends Controller {
public function pay(Request $request, PaymentGateway $gateway) {
$gateway->pay($request->amount);
return redirect('/success');
}
}
Subclasses should be substitutable for their parent classes without breaking the system.
Bad Example: A Bird
class with a fly()
method, but not all birds can fly.
class Bird {
public function fly() { /* Fly logic */ }
}
class Duck extends Bird { } // Works
class Penguin extends Bird { } // Penguins can't fly! 🐧
Good Example (Laravel Context): Use interfaces to define specific behaviors.
interface Flyable {
public function fly();
}
class Duck implements Flyable {
public function fly() { /* Fly logic */ }
}
class Penguin { } // No fly() method here
Clients shouldn’t depend on interfaces they don’t use.
Bad Example: A bloated Worker
interface forcing all classes to implement unnecessary methods.
interface Worker {
public function work();
public function sleep();
}
class Developer implements Worker {
public function work() { /* Coding */ }
public function sleep() { /* Not needed */ }
}
Good Example: Split interfaces into smaller, focused ones.
interface Workable {
public function work();
}
interface Sleepable {
public function sleep();
}
class Developer implements Workable {
public function work() { /* Coding */ }
}
class Manager implements Workable, Sleepable {
public function work() { /* Managing */ }
public function sleep() { /* Sleeping */ }
}
Depend on abstractions (interfaces), not concretions (classes).
Bad Example: Directly depending on a database class.
class MySQLDatabase {
public function query() { /* MySQL logic */ }
}
class ReportService {
private $database;
public function __construct() {
$this->database = new MySQLDatabase(); // Tight coupling
}
}
Good Example: Inject dependencies via interfaces.
interface Database {
public function query();
}
class MySQLDatabase implements Database {
public function query() { /* MySQL logic */ }
}
class CSVDatabase implements Database {
public function query() { /* CSV logic */ }
}
class ReportService {
private $database;
public function __construct(Database $database) {
$this->database = $database; // Loose coupling
}
}
// Usage in Laravel (bind in AppServiceProvider):
$this->app->bind(Database::class, MySQLDatabase::class);
By following SOLID principles, your Laravel apps will become cleaner, more flexible, and easier to collaborate on. Happy coding! 🚀
Amar Chand
Feb 28, 2025 09:12 PM