2 min read

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

A practical guide to applying SOLID principles in real-world Laravel applications, with concrete examples and architectural best practices.
SOLID in Laravel: What It Actually Looks Like in Real Code

Most Laravel apps start clean.

Six months later, controllers are 500 lines long, models know too much
about the world, and "just one more quick change" becomes a risky
operation.

Laravel gives you powerful tools.
SOLID gives you discipline.

Let's look at what SOLID actually means in a Laravel application --- not
in theory, but in real production structure.


The SOLID Principles (Quick Refresher)

  • S -- Single Responsibility Principle
    A class should have one reason to change.

  • O -- Open/Closed Principle
    Software entities should be open for extension but closed for
    modification.

  • L -- Liskov Substitution Principle
    Subtypes must be substitutable for their base types.

  • I -- Interface Segregation Principle
    Clients should not be forced to depend on methods they do not use.

  • D -- Dependency Inversion Principle
    Depend on abstractions, not concrete implementations.

Now let's map that to Laravel.


S --- Single Responsibility in Laravel

❌ Fat Controller

public function store(Request $request)
{
    $validated = $request->validate([
        'email' => 'required|email',
        'name' => 'required|string'
    ]);

    $user = User::create($validated);

    Mail::to($user->email)->send(new WelcomeMail($user));

    return response()->json($user);
}

This controller: - Validates - Creates data - Sends mail - Returns
formatted output

Too many responsibilities.


✅ Cleaner Structure

Form Request Handles validation & authorization.

Service Handles business logic.

Resource Formats output.

public function store(StoreUserRequest $request, UserService $service)
{
    $user = $service->createUser($request->validated());

    return new UserResource($user);
}

Now:

  • Controllers coordinate.
  • Requests validate.
  • Services execute business rules.
  • Resources transform output.
  • Models represent data --- not workflows.

That's SRP in Laravel.


O --- Open/Closed Principle

Let's say you support multiple payment processors.

interface PaymentProcessor
{
    public function charge(Order $order): void;
}
class StripeProcessor implements PaymentProcessor
{
    public function charge(Order $order): void
    {
        // Stripe logic
    }
}
class PayPalProcessor implements PaymentProcessor
{
    public function charge(Order $order): void
    {
        // PayPal logic
    }
}

Bind it in a service provider:

$this->app->bind(PaymentProcessor::class, StripeProcessor::class);

Now you can extend behavior without modifying the calling code.

That's Open/Closed done right.


L --- Liskov Substitution Principle

If something implements a contract, it should behave as expected.

If StripeProcessor implements PaymentProcessor, it must fully honor
that contract.

Don't override behavior in child classes in a way that changes meaning.

If your code depends on PaymentProcessor, it should not care which
processor it gets.


I --- Interface Segregation

Avoid "God interfaces."

Bad:

interface UserManager
{
    public function create();
    public function update();
    public function delete();
    public function exportCsv();
    public function sendNewsletter();
}

Better: split responsibilities.

  • UserWriter
  • UserExporter
  • UserNotifier

Keep contracts small and focused.

Laravel's contracts follow this pattern well.


D --- Dependency Inversion

Instead of this:

public function __construct(StripeProcessor $processor)

Do this:

public function __construct(PaymentProcessor $processor)

Then bind the implementation in a provider.

Now your business logic depends on abstractions.

Testing becomes easier.
Swapping implementations becomes trivial.

This is where Laravel's container shines.


Laravel Components & Their Responsibilities

Component Responsibility


Controller Coordinate request and response
Form Request Validation and authorization
Model Represent data and relationships
Service Business rules and orchestration
Resource Output transformation
Job Background execution
Event Signal that something happened
Listener React to an event
Policy Authorization logic
Facade Convenience access, not core domain logic

Respecting these boundaries keeps your app maintainable.


A Warning: SOLID Does Not Mean Class Explosion

SOLID is about clarity, not ceremony.

If you create:

  • UserCreator\
  • UserValidator\
  • UserLogger\
  • UserEventDispatcher

...for a feature that does not need it, you are adding noise.

Use structure where complexity demands it.

Discipline should reduce friction, not create it.


Final Thoughts

Laravel gives you the tools.
SOLID gives you the guardrails.

Together, they produce applications that survive version two --- and
version five.

Structure is not about being clever.
It's about making future changes boring and safe.

That's real architecture.