2 min read

SOLID Principles: A Practical Developer's Guide

A detailed, practical explanation of the SOLID principles with real-world examples and clear guidance for modern PHP and Laravel developers.
SOLID Principles: A Practical Developer's Guide

Many developers say they "know" SOLID.

Fewer can clearly explain each principle.

Even fewer consistently apply them in real-world projects.

This article breaks down each SOLID principle in practical terms with
real PHP examples. Then, in a companion article --- "SOLID in Laravel:
What It Actually Looks Like in Real Code"
--- we explore how these
principles show up inside a real Laravel application.

This guide focuses on clarity, depth, and practicality.


What Is SOLID?

SOLID is an acronym introduced by Robert C. Martin ("Uncle Bob") that
represents five design principles intended to make software:

  • Easier to understand\
  • Easier to change\
  • Easier to extend\
  • Less fragile as it grows

The five principles are:

  • S --- Single Responsibility Principle\
  • O --- Open / Closed Principle\
  • L --- Liskov Substitution Principle\
  • I --- Interface Segregation Principle\
  • D --- Dependency Inversion Principle

Let's break them down properly.


S --- Single Responsibility Principle (SRP)

Definition

A class should have one reason to change.

This does not mean: - One method - Tiny classes - Avoiding logic

It means a class should have one responsibility --- one core purpose.


Bad Example

class UserService
{
    public function createUser(array $data)
    {
        // save user
    }

    public function sendWelcomeEmail(User $user)
    {
        // send email
    }

    public function logAnalytics(User $user)
    {
        // log event
    }
}

Better Design

class UserCreator
{
    public function create(array $data): User
    {
        // create user
    }
}

class WelcomeEmailSender
{
    public function send(User $user): void
    {
        // send email
    }
}

class UserAnalyticsLogger
{
    public function log(User $user): void
    {
        // log analytics
    }
}

Each class now has a single responsibility.


O --- Open / Closed Principle (OCP)

Definition

Software entities should be open for extension, but closed for
modification.


Bad Example

class PaymentProcessor
{
    public function process(string $method, float $amount)
    {
        if ($method === 'stripe') {
            // stripe logic
        }

        if ($method === 'paypal') {
            // paypal logic
        }
    }
}

Better Design

interface PaymentMethod
{
    public function charge(float $amount): void;
}

class StripePayment implements PaymentMethod
{
    public function charge(float $amount): void
    {
        // stripe logic
    }
}

class PayPalPayment implements PaymentMethod
{
    public function charge(float $amount): void
    {
        // paypal logic
    }
}

class PaymentProcessor
{
    public function process(PaymentMethod $method, float $amount)
    {
        $method->charge($amount);
    }
}

L --- Liskov Substitution Principle (LSP)

Definition

Objects of a superclass should be replaceable with objects of a
subclass without breaking the application.


Violation Example

class Bird
{
    public function fly()
    {
        // flying logic
    }
}

class Penguin extends Bird
{
    public function fly()
    {
        throw new Exception("Penguins can't fly.");
    }
}

Improved Design

interface Bird {}

interface FlyingBird extends Bird
{
    public function fly();
}

class Sparrow implements FlyingBird
{
    public function fly() {}
}

class Penguin implements Bird
{
}

I --- Interface Segregation Principle (ISP)

Definition

Clients should not be forced to depend on interfaces they do not use.


Bad Example

interface Worker
{
    public function work();
    public function eat();
}

Better Design

interface Workable
{
    public function work();
}

interface Eatable
{
    public function eat();
}

class Human implements Workable, Eatable {}
class Robot implements Workable {}

D --- Dependency Inversion Principle (DIP)

Definition

High-level modules should not depend on low-level modules. Both should
depend on abstractions.


Bad Example

class OrderService
{
    protected StripePayment $payment;

    public function __construct()
    {
        $this->payment = new StripePayment();
    }
}

Better Design

class OrderService
{
    protected PaymentMethod $payment;

    public function __construct(PaymentMethod $payment)
    {
        $this->payment = $payment;
    }
}

How This Connects to Laravel

Laravel's service container makes Dependency Inversion natural.

Controllers stay small with SRP.

Interfaces and contracts support OCP and ISP.

For a real-world Laravel breakdown, read:

"SOLID in Laravel: What It Actually Looks Like in Real Code."


Further Reading


SOLID is not about perfection.

It is about survivable growth.