Understanding SOLID Design Principles in Laravel (For Beginners)

Author

Kritim Yantra

Feb 24, 2025

Understanding SOLID Design Principles in Laravel (For Beginners)

Understanding SOLID Design Principles in Laravel (For Beginners)

SOLID design principles are a set of guidelines that help developers write clean, maintainable, and scalable code. In this blog, we’ll break down each principle in simple terms and see how to apply them in Laravel with easy examples. Let’s dive in!


1. Single Responsibility Principle (SRP)

A class should have only one reason to change (i.e., do one thing).

Bad Example: A Laravel controller handling validation, business logic, and database operations.

class UserController extends Controller {
    public function store(Request $request) {
        // Validate
        $request->validate(['email' => 'required|email']);

        // Business logic
        $user = new User();
        $user->email = $request->email;
        $user->save();

        // Send welcome email
        Mail::to($user)->send(new WelcomeEmail());

        return redirect('/users');
    }
}

Good Example: Split responsibilities into dedicated classes.

  • Controller: Handles HTTP logic.
  • Form Request: Handles validation.
  • Service Class: Handles business logic.
// Step 1: Create a Form Request
class StoreUserRequest extends FormRequest {
    public function rules() {
        return ['email' => 'required|email'];
    }
}

// Step 2: Create a Service Class
class UserService {
    public function createUser(array $data): User {
        $user = User::create($data);
        Mail::to($user)->send(new WelcomeEmail());
        return $user;
    }
}

// Step 3: Simplified Controller
class UserController extends Controller {
    public function store(StoreUserRequest $request, UserService $service) {
        $service->createUser($request->validated());
        return redirect('/users');
    }
}

2. Open/Closed Principle (OCP)

Classes should be open for extension but closed for modification.

Bad Example: A payment processor that changes every time a new gateway is added.

class PaymentController extends Controller {
    public function pay(Request $request) {
        $gateway = $request->gateway;

        if ($gateway === 'stripe') {
            // Stripe logic
        } elseif ($gateway === 'paypal') {
            // PayPal logic
        }
    }
}

Good Example: Use an interface to allow new payment gateways without altering existing code.

interface PaymentGateway {
    public function pay(float $amount);
}

class StripeGateway implements PaymentGateway {
    public function pay(float $amount) { /* Stripe logic */ }
}

class PayPalGateway implements PaymentGateway {
    public function pay(float $amount) { /* PayPal logic */ }
}

class PaymentController extends Controller {
    public function pay(Request $request, PaymentGateway $gateway) {
        $gateway->pay($request->amount);
        return redirect('/success');
    }
}

3. Liskov Substitution Principle (LSP)

Subclasses should be substitutable for their parent classes without breaking the system.

Bad Example: A Bird class with a fly() method, but not all birds can fly.

class Bird {
    public function fly() { /* Fly logic */ }
}

class Duck extends Bird { } // Works

class Penguin extends Bird { } // Penguins can't fly! 🐧

Good Example (Laravel Context): Use interfaces to define specific behaviors.

interface Flyable {
    public function fly();
}

class Duck implements Flyable {
    public function fly() { /* Fly logic */ }
}

class Penguin { } // No fly() method here

4. Interface Segregation Principle (ISP)

Clients shouldn’t depend on interfaces they don’t use.

Bad Example: A bloated Worker interface forcing all classes to implement unnecessary methods.

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

class Developer implements Worker {
    public function work() { /* Coding */ }
    public function sleep() { /* Not needed */ }
}

Good Example: Split interfaces into smaller, focused ones.

interface Workable {
    public function work();
}

interface Sleepable {
    public function sleep();
}

class Developer implements Workable {
    public function work() { /* Coding */ }
}

class Manager implements Workable, Sleepable {
    public function work() { /* Managing */ }
    public function sleep() { /* Sleeping */ }
}

5. Dependency Inversion Principle (DIP)

Depend on abstractions (interfaces), not concretions (classes).

Bad Example: Directly depending on a database class.

class MySQLDatabase {
    public function query() { /* MySQL logic */ }
}

class ReportService {
    private $database;

    public function __construct() {
        $this->database = new MySQLDatabase(); // Tight coupling
    }
}

Good Example: Inject dependencies via interfaces.

interface Database {
    public function query();
}

class MySQLDatabase implements Database {
    public function query() { /* MySQL logic */ }
}

class CSVDatabase implements Database {
    public function query() { /* CSV logic */ }
}

class ReportService {
    private $database;

    public function __construct(Database $database) {
        $this->database = $database; // Loose coupling
    }
}

// Usage in Laravel (bind in AppServiceProvider):
$this->app->bind(Database::class, MySQLDatabase::class);

Why SOLID Matters in Laravel

  • Maintainability: Changes are isolated and predictable.
  • Testability: Easier to mock dependencies.
  • Scalability: New features can be added without breaking old code.

By following SOLID principles, your Laravel apps will become cleaner, more flexible, and easier to collaborate on. Happy coding! 🚀

Tags

Laravel Php

Comments

Amar Chand

Amar Chand

Feb 28, 2025 09:12 PM

nice artical superb defiend. thanks

Please log in to post a comment:

Continue with Google

Related Posts