Intro
Writing maintainable, scalable, and clean code is essential for any developer. The SOLID principles help achieve this by enforcing good design patterns.
In this guide, we'll refactor parts of a real-world application to follow SOLID principles.
S - Singles Responsibility Principle (SRP)
each class should have only one reason to change.
❌ Bad Example: Service Doing too much
<?php
class OrderService {
public function processOrder($order) {
// Process order logic
echo "Order processed!";
// Sends email confirmation (SRP violation)
mail($order->customer_email, "Your order is confirmed!", "Details...");
// Logs action (SRP violation)
file_put_contents("logs.txt", "Order processed: " . $order->id);
}
}
🚨 Problem: This class is handling multiple responsibilities: processing, emailing and log.
✅ Refactored: Separate Responsibilities
<?php
class OrderProcessor {
public function process($order) {
echo "Order processed!";
}
}
class EmailService {
public function sendConfirmation($email) {
mail($email, "Your order is confirmed!", "Details...");
}
}
class Logger {
public function log($message) {
file_put_contents("logs.txt", $message);
}
}
Now, each class has a single responsibility, marking the system more maintainable.
O - Open/Closed Principle (OCP)
Code should be open for extension but closed for modification.
❌ Bad Example: Hardcoded Payment Methods
<?php
class PaymentProcessor {
public function pay($amount, $method) {
if ($method == 'credit_card') {
echo "Paid with credit card.";
} elseif ($method == 'paypal') {
echo "Paid with PayPal.";
}
}
}
🚨 Problem: Every time we add a new payment method, we have to modify this class.
✅ Refactored: Extensible Payment System
<?php
interface PaymentMethod {
public function pay($amount);
}
class CreditCardPayment implements PaymentMethod {
public function pay($amount) {
echo "Paid with credit card.";
}
}
class PayPalPayment implements PaymentMethod {
public function pay($amount) {
echo "Paid with PayPal.";
}
}
class PaymentProcessor {
public function processPayment(PaymentMethod $payment, $amount) {
$payment->pay($amount);
}
}
Now, we can add new payment methods without modifying existing code !
L - Liskov Substitution Principle (LSP)
Subclasses should be replaceable for their base classes without affecting functionality
❌ Bad Example: Plans with Unexpected Behavios
<?php
class Plan {
public function getBillingCycle() {
return "Monthly";
}
}
class LifetimePlan extends Plan {
public function getBillingCycle() {
return null; // No billing cycle for lifetime plans
}
}
🚨 Problem: LifetimePlan
violates expectations by returns null
✅ Refactored: Clear Separation of Conecepts
<?php
interface Plan {
public function getBillingDetails();
}
class MonthlyPlan implements Plan {
public function getBillingDetails() {
return "Billed Monthly";
}
}
class LifetimePlan implements Plan {
public function getBillingDetails() {
return "One-time Payment";
}
}
Now, each plan behaves predictably, following LSP
I - Interface Segregation Principle(ISP)
Classes should not be forced to implement methods they don't use.
❌ Bad Example: Forcing Unused Methods on All Users
<?php
interface User {
public function login();
public function manageSubscription();
}
class AdminUser implements User {
public function login() {
echo "Admin logged in.";
}
public function manageSubscription() {
echo "Managing user subscriptions.";
}
}
class RegularUser implements User {
public function login() {
echo "User logged in.";
}
public function manageSubscription() {
throw new Exception("Not allowed!");
}
}
🚨 Problem: Regular users shouldn’t have to implement manageSubscription().
✅ Refactored: Split Interfaces
<?php
interface Authenticatable {
public function login();
}
interface SubscriptionManager {
public function manageSubscription();
}
class AdminUser implements Authenticatable, SubscriptionManager {
public function login() {
echo "Admin logged in.";
}
public function manageSubscription() {
echo "Managing user subscriptions.";
}
}
class RegularUser implements Authenticatable {
public function login() {
echo "User logged in.";
}
}
Now, each class only implements the methods it actually needs.
D - Dependency Inversion Principle (DIP)
Depende on abstractions, not concrete implementations.
❌ Bad Example: Service Dependente on a Specific DB
<?php
class OrderRepository {
private $database;
public function __construct(MySQLDatabase $db) {
$this->database = $db;
}
}
🚨 Problem: Tightly coupled to MySQL, marking changes difficult.
✅ Refactored: Using an Abstraction
<?php
interface Database {
public function query($sql);
}
class MySQLDatabase implements Database {
public function query($sql) {
// Execute MySQL query
}
}
class PostgreSQLDatabase implements Database {
public function query($sql) {
// Execute PostgreSQL query
}
}
class OrderRepository {
private $database;
public function __construct(Database $db) {
$this->database = $db;
}
}
Now, any database can be injected, making the code flexible and adaptable.
Conclusion
Applying SOLID principles makes our code easier to maintain, scale, and extend.
By following these principles, we build software that is clean, reliable, and adaptable.
👉 Which SOLID principle do you find hardest to apply? Let’s discuss in the comments!
Top comments (0)