
Table of Contents
By Khimananda Oli | Last reviewed: August 2026
Most Laravel applications start simple but degrade into tightly coupled controllers and models as business logic grows. Applying clean architecture in Laravel real example patterns reverses this entropy by placing domain rules at the center and pushing frameworks, databases, and HTTP to the periphery. This guide walks through a production-grade implementation that keeps your core logic portable, testable, and independent of Eloquent or specific queue drivers.
What is clean architecture in Laravel real example structure?
Clean architecture enforces a strict dependency rule: outer layers depend on inner layers, never the reverse. In a Laravel context, this means your controllers (Interface Adapters) call application services (Use Cases), which orchestrate domain entities and repository interfaces. The infrastructure layer implements those interfaces using Eloquent, Redis, or external APIs. This inversion isolates business policy from technical details.
The directory structure typically mirrors these boundaries. Instead of grouping by technical concern (Models, Controllers), you group by feature or bounded context. A common mistake is creating an "Application" folder that becomes a dumping ground; keep use cases focused on single business operations. For teams transitioning from standard MVC, this shift feels verbose initially but pays dividends when requirements change. If you are also managing complex data stores, understanding MongoDB administration basics helps inform how repository abstractions should handle document versus relational paradigms.
How do you implement domain entities and value objects in Laravel?
Domain entities represent business concepts with identity, while value objects describe attributes without identity. Both must be free of Laravel facades, Eloquent traits, or validation helpers. They enforce invariants through constructors and methods, ensuring invalid states cannot exist.
Create pure PHP entities
<?php
declare(strict_types=1);
namespace App\Domain\Order;
final class Order
{
private function __construct(
private readonly string $id,
private OrderStatus $status,
private readonly Money $totalAmount
) {}
public static function create(string $id, Money $amount): self
{
return new self($id, OrderStatus::Pending, $amount);
}
public function confirm(): void
{
if ($this->status !== OrderStatus::Pending) {
throw new \DomainException('Only pending orders can be confirmed');
}
$this->status = OrderStatus::Confirmed;
}
public function status(): OrderStatus
{
return $this->status;
}
} Enforce invariants with value objects
Value objects like Money or EmailAddress validate themselves upon construction. This prevents primitive obsession and spreads validation logic across controllers. When your domain model is this strict, your Laravel testing with Pest in CI/CD becomes faster because you test business rules without booting the framework or hitting a database.
- Never extend Eloquent Model in domain entities
- Use enums for finite state sets (OrderStatus, PaymentType)
- Throw DomainException for invariant violations, not HTTP exceptions
- Keep entities serializable for event sourcing or caching needs
How do you wire use cases and dependency inversion in Laravel?
Use cases (or application services) orchestrate domain logic and infrastructure calls. They accept simple DTOs or primitives, never HTTP Request objects. Dependencies are injected via constructor using interfaces defined in the Application or Domain layer, with implementations bound in Laravel's service container.
Define the use case contract
<?php
declare(strict_types=1);
namespace App\Application\Order;
interface ConfirmOrderUseCaseInterface
{
public function execute(ConfirmOrderCommand $command): OrderDto;
}
final readonly class ConfirmOrderCommand
{
public function __construct(
public string $orderId,
public string $confirmedByUserId
) {}
} Implement and bind in ServiceProvider
The implementation retrieves the entity via repository, calls domain methods, persists changes, and dispatches events. Bind the interface to the concrete class in AppServiceProvider. This separation allows you to swap Eloquent for a DynamoDB adapter later without touching controllers or use cases. Teams adopting CI/CD pipelines with GitLab CI for Laravel benefit here because integration tests can use fake repositories while unit tests verify pure domain logic instantly.
How do you build infrastructure adapters without leaking framework details?
Infrastructure adapters translate between your domain and external tools. The most critical rule: domain entities never know about Eloquent. Map database models to domain entities inside the repository implementation, not in the entity itself.
Eloquent repository mapper pattern
<?php
declare(strict_types=1);
namespace App\Infrastructure\Persistence\Eloquent;
use App\Domain\Order\Order;
use App\Domain\Order\OrderRepositoryInterface;
use App\Models\OrderModel;
final class EloquentOrderRepository implements OrderRepositoryInterface
{
public function findById(string $id): ?Order
{
$model = OrderModel::find($id);
return $model ? $this->toDomain($model) : null;
}
public function save(Order $order): void
{
$model = OrderModel::updateOrCreate(
['id' => $order->id()],
[
'status' => $order->status()->value,
'total_amount' => $order->totalAmount()->toCents(),
]
);
}
private function toDomain(OrderModel $m): Order
{
return Order::reconstitute(
$m->id,
OrderStatus::from($m->status),
Money::fromCents($m->total_amount)
);
}
} This mapping overhead is the tax you pay for decoupling. It feels redundant for CRUD apps but prevents ORM changes from rippling through business logic. When optimizing performance, remember that Laravel performance optimization techniques like eager loading belong in the repository, not the domain. You can add query optimizations here without polluting use cases.
| Aspect | Standard Laravel MVC | Clean Architecture Approach |
|---|---|---|
| Business Logic Location | Controllers, Models, Form Requests | Domain Entities + Use Cases |
| Database Coupling | Eloquent embedded in models | Repository abstraction with mappers |
| Testability | Requires HTTP/DB bootstrapping | Pure unit tests for domain, fakes for infra |
| Framework Upgrades | High risk, touches business code | Low risk, only adapter layer affected |
| Onboarding Complexity | Low initial, high long-term | Higher initial, predictable long-term |
When should you avoid clean architecture in Laravel projects?
Clean architecture adds indirection. For simple CRUD APIs, admin panels, or prototypes with stable requirements, standard MVC with service classes is often sufficient. The overhead of mappers, DTOs, and layered directories slows delivery when domain complexity is low.
Adopt clean architecture when you observe: multiple teams modifying the same models, frequent requirement changes in core business rules, need to support multiple delivery mechanisms (API, CLI, events), or compliance requirements demanding audit trails separate from storage. For Nepali fintech or e-commerce platforms handling payments and regulatory reporting, this separation often justifies the upfront cost. Conversely, internal tools with three developers and stable specs rarely need full DDD.
Practical next steps for adopting clean architecture in Laravel
Start incrementally. Extract one complex feature (order processing, payment reconciliation) into clean architecture layers while leaving simpler modules in MVC. Write characterization tests first to capture existing behavior before refactoring. Establish coding standards for entity purity and repository contracts early to prevent drift.
- Audit current pain points: identify controllers over 200 lines or models with business methods
- Create a
src/Domainnamespace outsideapp/to enforce boundary discipline - Write failing domain tests for invariants before extracting entities
- Build repository interfaces based on use case needs, not database schema
- Map Eloquent models to domain entities in infrastructure layer
- Bind interfaces in ServiceProvider and inject into use cases
- Add integration tests using test doubles for external services
Clean architecture in Laravel real example patterns are not about perfection but about managing change velocity. The goal is making future modifications cheaper than today's shortcuts. If your team struggles with flaky tests, slow onboarding, or fear of deploying core features, the layered approach addresses root causes rather than symptoms. Reach out via contact me if you need help assessing whether your Laravel codebase warrants this transition or want a targeted architecture review.