What is SOLID?

SOLID is an acronym that represents five fundamental principles of object-oriented design. They were introduced by Robert C. Martin ("Uncle Bob"). Following these principles helps developers create software that is easier to maintain, understand, and extend. Think of them not as strict rules, but as powerful guidelines for writing clean, professional code.


S - Single Responsibility Principle (SRP)

The Idea: A class should have only one reason to change.

Analogy: Think of a Swiss Army Knife. It does many things (cuts, opens cans, drives screws). If you need to change the screwdriver, you have to modify the whole tool. This violates SRP. A dedicated screwdriver, on the other hand, has only one job. It adheres to SRP.

Problem: A class that handles both user data and error logging.


// VIOLATES SRP
class User {
    public function save() { /* ... save user to database ... */ }
    public function logError($error) { /* ... write error to a log file ... */ }
}

Solution: Split the responsibilities into separate classes.


// ADHERES TO SRP
class User {
    public function save() { /* ... save user to database ... */ }
}

class Logger {
    public function logError($error) { /* ... write error to a log file ... */ }
}

O - Open/Closed Principle (OCP)

The Idea: Software entities (classes, modules, functions) should be open for extension, but closed for modification.

Analogy: Your computer has USB ports. It is open to extension because you can plug in new devices (webcam, keyboard). But it is closed for modification because you don't need to open the case and solder the motherboard to add a new device.

Problem: A shape calculator that needs to be modified every time you add a new shape.


// VIOLATES OCP
class AreaCalculator {
    public function calculate($shapes) {
        foreach ($shapes as $shape) {
            if ($shape instanceof Square) { /* calculate square area */ }
            if ($shape instanceof Circle) { /* calculate circle area */ }
            // If we add a Triangle, we MUST MODIFY this file!
        }
    }
}

Solution: Use an interface and let each shape calculate its own area.


// ADHERES TO OCP
interface Shape {
    public function area();
}
class Square implements Shape {
    public function area() { /* ... */ }
}
class Circle implements Shape {
    public function area() { /* ... */ }
}

class AreaCalculator {
    public function calculate($shapes) {
        foreach ($shapes as $shape) {
            // This class no longer needs to know about specific shapes.
            $shape->area(); 
        }
    }
}

L - Liskov Substitution Principle (LSP)

The Idea: Subtypes must be substitutable for their base types without altering the correctness of the program.

Analogy: If you have a remote control for a "Television" (base type), it should work perfectly fine for a "Smart TV" (subtype) without causing unexpected results. If pressing the "volume up" button on the Smart TV mutes the sound, it violates LSP.

Problem: The classic Rectangle/Square problem. A square "is-a" rectangle, right? Let's see.


// VIOLATES LSP
class Rectangle {
    public function setWidth($width) { $this->width = $width; }
    public function setHeight($height) { $this->height = $height; }
}
class Square extends Rectangle {
    // A square must have equal sides, so we override the methods.
    public function setWidth($width) { $this->width = $this->height = $width; }
    public function setHeight($height) { $this->width = $this->height = $height; }
}

function clientCode(Rectangle $r) {
    $r->setWidth(5);
    $r->setHeight(4);
    // The client code EXPECTS the area to be 5 * 4 = 20.
    // But if you pass a Square object, the area will be 4 * 4 = 16.
    // The program's correctness is altered!
}

Solution: Inheritance must model a true "is-a" relationship based on behavior, not just properties. In this case, a Square is not a valid substitute for a Rectangle because its behavior is different.


I - Interface Segregation Principle (ISP)

The Idea: No client should be forced to depend on methods it does not use. It's better to have many small, specific interfaces than one large, general-purpose one.

Analogy: Instead of a "Mega Multi-Function Machine" interface with print, scan, and fax methods, it's better to have separate Printable, Scannable, and Faxable interfaces. A simple printer only needs to implement Printable.

Problem: A giant Worker interface.


// VIOLATES ISP
interface Worker {
    public function work();
    public function eat();
}
class Human implements Worker { /* ... implements both methods ... */ }
class Robot implements Worker {
    public function work() { /* ... */ }
    public function eat() { /* Robots don't eat! This method is useless. */ }
}

Solution: Break the interface into smaller, more relevant pieces.


// ADHERES TO ISP
interface Workable { public function work(); }
interface Feedable { public function eat(); }

class Human implements Workable, Feedable { /* ... */ }
class Robot implements Workable { /* ... only implements what it needs ... */ }

D - Dependency Inversion Principle (DIP)

The Idea: High-level modules should not depend on low-level modules. Both should depend on abstractions (like interfaces).

Analogy: A lamp isn't permanently wired into your wall. It has a plug (an abstraction). You can plug that lamp into any wall socket that fits the plug's standard (the interface). The lamp (high-level) doesn't care about the specific wiring inside your wall (low-level), it only cares about the interface (the socket).

Problem: A high-level class directly creating a low-level object. This is called tight coupling.


// VIOLATES DIP
class PasswordReminder {
    private $dbConnection;
    public function __construct() {
        // This high-level class DEPENDS ON a specific low-level class.
        $this->dbConnection = new MySQLConnection(); 
    }
}

Solution: Depend on an interface instead. Pass the dependency in (this is called Dependency Injection).


// ADHERES TO DIP
interface DBConnectionInterface { public function connect(); }

class MySQLConnection implements DBConnectionInterface { /* ... */ }

class PasswordReminder {
    private $dbConnection;
    
    // Now we depend on the abstraction (interface), not the concrete class.
    public function __construct(DBConnectionInterface $dbConnection) {
        $this->dbConnection = $dbConnection;
    }
}

// The low-level object is created outside and "injected" in.
$mysqlConnection = new MySQLConnection();
$reminder = new PasswordReminder($mysqlConnection);

Conclusion

You've done it! This completes our deep dive into Object-Oriented PHP. These SOLID principles might seem abstract at first, but as you build larger applications, they will become your most valuable tools for writing code that is a pleasure to work with, not a pain to maintain.