Skip OOP Basics?
Are you comfortable with all of the following?
- Classes, objects, and constructors
- Public, protected, and private visibility
- Interfaces vs abstract classes
- Type hinting and return types
- Late static binding
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.
Complete the code below to pass all test cases.