DEV Community

Aleson França
Aleson França

Posted on

S.O.L.I.D Principles - A Real World Guide

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);
    }
}
Enter fullscreen mode Exit fullscreen mode

🚨 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);
    }
}
Enter fullscreen mode Exit fullscreen mode

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.";
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

🚨 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);
    }
}
Enter fullscreen mode Exit fullscreen mode

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
    }
}
Enter fullscreen mode Exit fullscreen mode

🚨 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";
    }
}
Enter fullscreen mode Exit fullscreen mode

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!");
    }
}
Enter fullscreen mode Exit fullscreen mode

🚨 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.";
    }
}
Enter fullscreen mode Exit fullscreen mode

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;
    }
}
Enter fullscreen mode Exit fullscreen mode

🚨 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;
    }
}
Enter fullscreen mode Exit fullscreen mode

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)