← Back to Dashboard

OOP Prerequisite Path

SRP, OCP & DIP

OOP Intermediate — SRP, OCP & DIP

Now that we have the basics down, let's level up with three of the five SOLID principles. These three — Single Responsibility, Open/Closed, and Dependency Inversion — are the ones you'll exercise directly in Modules 1–3. The remaining two principles (Liskov Substitution and Interface Segregation) plus Composition are covered in Stage 0C.


S — Single Responsibility Principle (SRP)

A class should have only one reason to change.

Violation

// ❌ This class does THREE things: validates, processes, and sends receipts
class PaymentProcessor {
    public function processPayment(array $data): bool {
        // Validate
        if (empty($data['amount']) || $data['amount'] <= 0) {
            throw new \InvalidArgumentException('Invalid amount');
        }

        // Process
        echo "Processing payment of {$data['amount']}\n";

        // Send receipt
        echo "Sending receipt to {$data['email']}\n";
        return true;
    }
}

Fix

// ✅ Each class has one job
class PaymentValidator {
    public function validate(array $data): bool {
        return !empty($data['amount']) && $data['amount'] > 0;
    }
}

class PaymentGateway {
    public function charge(float $amount): bool {
        echo "Processing payment of {$amount}\n";
        return true;
    }
}

class ReceiptMailer {
    public function send(string $email, float $amount): void {
        echo "Sending receipt to {$email}\n";
    }
}

O — Open/Closed Principle (OCP)

Classes should be open for extension but closed for modification.

Violation

// ❌ Adding a new discount type requires editing this method
class DiscountCalculator {
    public function calculate(string $type, float $price): float {
        if ($type === 'senior') return $price * 0.80;
        if ($type === 'student') return $price * 0.90;
        if ($type === 'employee') return $price * 0.75; // had to modify!
        return $price;
    }
}

Fix

// ✅ New discounts = new classes, no modification needed
interface DiscountInterface {
    public function apply(float $price): float;
}

class SeniorDiscount implements DiscountInterface {
    public function apply(float $price): float { return $price * 0.80; }
}

class StudentDiscount implements DiscountInterface {
    public function apply(float $price): float { return $price * 0.90; }
}

// Adding EmployeeDiscount doesn't touch existing code

D — Dependency Inversion Principle (DIP)

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

Violation

// ❌ OrderService depends directly on MySqlOrderRepository
class OrderService {
    private MySqlOrderRepository $repo;

    public function __construct() {
        $this->repo = new MySqlOrderRepository();
    }
}

Fix

// ✅ Depends on an interface, not a concrete class
interface OrderRepositoryInterface {
    public function save(array $data): int;
}

class OrderService {
    public function __construct(
        private OrderRepositoryInterface $repo,
    ) {}
}

Now OrderService works with any repository implementation — MySQL, PostgreSQL, in-memory, API-backed — without changes.


Why These Three First?

SRP, OCP, and DIP are the principles you'll use most immediately:

  • Module 1 (DI) is DIP in action — injecting dependencies through constructors
  • Module 2 (Service) is SRP applied — each service class has one job
  • Module 3 (Repository) combines DIP (interface-driven) and SRP (data access separated from business logic)

The challenge below tests SRP and DIP together. Stage 0C covers the remaining principles.

What You Learned

  • SRP means each class has one reason to change — split classes that do too many things
  • OCP means adding behavior shouldn't require modifying existing code — use interfaces and new classes instead
  • DIP means depending on abstractions (interfaces), not concrete implementations
  • These three principles directly prepare you for Modules 1–3

SRP, OCP & DIP in Laravel

Laravel follows these three SOLID principles throughout its architecture. Understanding where they appear helps you write code that works with the framework rather than against it.

SRP — Controllers, Services, Requests

Laravel encourages thin controllers by providing Form Requests for validation and service classes for business logic:

// Each class has one responsibility
class StorePaymentRequest extends FormRequest {
    // Validation only
    public function rules(): array {
        return ['amount' => 'required|numeric|min:0.01'];
    }
}

class PaymentService {
    // Business logic only
    public function process(float $amount): Receipt { /* ... */ }
}

class PaymentController extends Controller {
    // HTTP orchestration only
    public function store(StorePaymentRequest $request, PaymentService $service) {
        $receipt = $service->process($request->validated()['amount']);
        return response()->json($receipt);
    }
}

OCP — Middleware

Laravel's middleware pipeline is a perfect example of OCP. You add new behavior (logging, rate limiting, CORS) without modifying the core HTTP kernel:

// Adding new behavior = adding a new class, not editing existing ones
class LogRequestMiddleware {
    public function handle(Request $request, Closure $next) {
        Log::info('Request: ' . $request->path());
        return $next($request);
    }
}

Register it in bootstrap/app.php and the pipeline picks it up — no existing code changed.

DIP — Service Container

The service container is Laravel's answer to DIP. You bind interfaces to implementations in service providers:

// In a ServiceProvider
$this->app->bind(PaymentGatewayInterface::class, StripeGateway::class);

Now any class that type-hints PaymentGatewayInterface gets a StripeGateway — and switching to a different gateway is a one-line change.

This example is pre-filled and runnable. Click "Run" to see the output.

Output

                

            

Complete the code below to pass all test cases.