solid-principles

star 7

Deep dive into SOLID principles with PHP/TypeScript examples, refactoring patterns, and violation detection

zb-ss By zb-ss schedule Updated 2/9/2026

name: solid-principles description: Deep dive into SOLID principles with PHP/TypeScript examples, refactoring patterns, and violation detection license: MIT compatibility: opencode metadata: type: architectural languages: php, typescript

Single Responsibility Principle (SRP)

Definition: A class should have only one reason to change.

Violation Example

class UserService
{
    public function createUser(array $data): User
    {
        // Validates data
        // Creates user in DB
        // Sends welcome email
        // Logs the action
        // Generates PDF report
    }
}

Refactored

final class UserService
{
    public function __construct(
        private readonly UserValidator $validator,
        private readonly UserRepository $repository,
        private readonly EventDispatcherInterface $dispatcher
    ) {}
    
    public function createUser(array $data): User
    {
        $this->validator->validate($data);
        $user = $this->repository->create($data);
        $this->dispatcher->dispatch(new UserCreated($user));
        return $user;
    }
}

// Separate concerns
final class WelcomeEmailListener { /* handles UserCreated */ }
final class UserActivityLogger { /* handles UserCreated */ }

Open/Closed Principle (OCP)

Definition: Open for extension, closed for modification.

Violation Example

class PaymentProcessor
{
    public function process(string $type, float $amount): void
    {
        if ($type === 'credit_card') {
            // Credit card logic
        } elseif ($type === 'paypal') {
            // PayPal logic
        } elseif ($type === 'stripe') {
            // Adding new payment = modifying this class!
        }
    }
}

Refactored (Strategy Pattern)

interface PaymentGatewayInterface
{
    public function process(float $amount): PaymentResult;
    public function supports(string $type): bool;
}

final class CreditCardGateway implements PaymentGatewayInterface { /* ... */ }
final class PayPalGateway implements PaymentGatewayInterface { /* ... */ }
final class StripeGateway implements PaymentGatewayInterface { /* ... */ }

final class PaymentProcessor
{
    /** @param PaymentGatewayInterface[] $gateways */
    public function __construct(private readonly iterable $gateways) {}
    
    public function process(string $type, float $amount): PaymentResult
    {
        foreach ($this->gateways as $gateway) {
            if ($gateway->supports($type)) {
                return $gateway->process($amount);
            }
        }
        throw new UnsupportedPaymentTypeException($type);
    }
}

Liskov Substitution Principle (LSP)

Definition: Subtypes must be substitutable for their base types.

Violation Example

class Rectangle
{
    protected int $width;
    protected int $height;
    
    public function setWidth(int $width): void { $this->width = $width; }
    public function setHeight(int $height): void { $this->height = $height; }
    public function getArea(): int { return $this->width * $this->height; }
}

class Square extends Rectangle
{
    public function setWidth(int $width): void
    {
        $this->width = $width;
        $this->height = $width; // Breaks LSP!
    }
}

// This code breaks with Square:
function doubleWidth(Rectangle $rect): void
{
    $rect->setWidth($rect->getWidth() * 2);
    // Expected: width doubled, height unchanged
    // Square: both changed - unexpected behavior!
}

Refactored

interface ShapeInterface
{
    public function getArea(): int;
}

final class Rectangle implements ShapeInterface
{
    public function __construct(
        private readonly int $width,
        private readonly int $height
    ) {}
    
    public function getArea(): int
    {
        return $this->width * $this->height;
    }
}

final class Square implements ShapeInterface
{
    public function __construct(private readonly int $side) {}
    
    public function getArea(): int
    {
        return $this->side * $this->side;
    }
}

Interface Segregation Principle (ISP)

Definition: Clients shouldn't depend on interfaces they don't use.

Violation Example

interface WorkerInterface
{
    public function work(): void;
    public function eat(): void;
    public function sleep(): void;
    public function attendMeeting(): void;
}

class Robot implements WorkerInterface
{
    public function work(): void { /* OK */ }
    public function eat(): void { /* Robots don't eat! */ }
    public function sleep(): void { /* Robots don't sleep! */ }
    public function attendMeeting(): void { /* Maybe? */ }
}

Refactored

interface WorkableInterface
{
    public function work(): void;
}

interface FeedableInterface
{
    public function eat(): void;
}

interface RestableInterface
{
    public function sleep(): void;
}

final class Human implements WorkableInterface, FeedableInterface, RestableInterface
{
    public function work(): void { /* ... */ }
    public function eat(): void { /* ... */ }
    public function sleep(): void { /* ... */ }
}

final class Robot implements WorkableInterface
{
    public function work(): void { /* ... */ }
}

Dependency Inversion Principle (DIP)

Definition: Depend on abstractions, not concretions.

Violation Example

class OrderService
{
    private MySQLDatabase $database;
    private SmtpMailer $mailer;
    
    public function __construct()
    {
        $this->database = new MySQLDatabase(); // Tight coupling!
        $this->mailer = new SmtpMailer();      // Can't test!
    }
}

Refactored

interface OrderRepositoryInterface
{
    public function save(Order $order): void;
    public function find(int $id): ?Order;
}

interface MailerInterface
{
    public function send(string $to, string $subject, string $body): void;
}

final class OrderService
{
    public function __construct(
        private readonly OrderRepositoryInterface $repository,
        private readonly MailerInterface $mailer
    ) {}
    
    public function placeOrder(Order $order): void
    {
        $this->repository->save($order);
        $this->mailer->send(
            $order->getCustomerEmail(),
            'Order Confirmation',
            'Your order has been placed.'
        );
    }
}

// Now you can inject different implementations:
// - MySQLOrderRepository or PostgresOrderRepository
// - SmtpMailer or SendGridMailer or TestMailer

Quick Detection Checklist

Principle Smell Fix
SRP Class has multiple and in description Split into focused classes
OCP Adding features requires modifying existing code Use interfaces/strategy
LSP Subclass throws unexpected exceptions Composition over inheritance
ISP Class implements methods it doesn't need Split interface
DIP new keyword for dependencies Constructor injection

TypeScript Equivalents

// DIP with interfaces
interface UserRepository {
  find(id: number): Promise<User | null>
  save(user: User): Promise<void>
}

// OCP with strategy
type PaymentStrategy = (amount: number) => Promise<PaymentResult>

const strategies: Record<string, PaymentStrategy> = {
  stripe: processStripe,
  paypal: processPayPal,
}

function processPayment(type: string, amount: number): Promise<PaymentResult> {
  const strategy = strategies[type]
  if (!strategy) throw new Error(`Unknown payment type: ${type}`)
  return strategy(amount)
}
Install via CLI
npx skills add https://github.com/zb-ss/opencode-workflows --skill solid-principles
Repository Details
star Stars 7
call_split Forks 0
navigation Branch main
article Path SKILL.md
More from Creator