DEV Community

Aleson França
Aleson França

Posted on

Applying Clean Arch in Laravel

Clean Arch is an architectural model that helps structure applications in an organized way, separating responsibilities and facilitating code maintenance. In this post, we will explore how to apply Clean Arch in laravel.

Principles of Clean Architecture

Clean Architecture proposes separating the application into well-defined layers:

  • Entities: Pure and independent business rules.
  • Use Cases: Implement the application logic.
  • Interfaces and Adapters: Communication between use cases and infrastructure (HTTP, database, etc.).
  • Frameworks & Drivers: Laravel, Eloquent, Controllers and any specific implementation technology.

This approach reduces coupling and facilitates system evolution without impacting the entire structure.

Example

To provide a clear illustration, a simple reservation system will serve as our example.


To provide a clear illustration, a simple reservation system will serve as our example.

Example: The entity represents the business rule for booking and does not depend on framework:

<?php

namespace App\Domain\Entities;

class Booking
{
    public function __construct(
        public int $userId,
        public int $serviceId,
        public string $date
    ) {
        // Business rule can be applied here
    }
}
Enter fullscreen mode Exit fullscreen mode

Creating the repository interface

The repository defines persistence but does not depend on Eloquent:

<?php

namespace App\Domain\Repositories;

use App\Domain\Entities\Booking;

interface BookingRepositoryInterface
{
    public function save(Booking $booking): Booking;
}
Enter fullscreen mode Exit fullscreen mode

Implementing the Create Booking UseCase

The booking creation logic is isolated in a use case:

<?php

namespace App\Application\UseCases;

use App\Domain\Entities\Booking;
use App\Domain\Repositories\BookingRepositoryInterface;

class CreateBooking
{
    public function __construct(private BookingRepositoryInterface $repository) {}

    public function execute(array $data): Booking
    {
        if (strtotime(data_get($data, 'date')) < time()) {
            throw new \Exception("It is not possible to book for past dates.");
        }

        $booking = new Booking(data_get($data, 'user_id'), data_get($data, 'service_id'), data_get($data, 'date'));
        return $this->repository->save($booking);
    }
}
Enter fullscreen mode Exit fullscreen mode

Implementing the Eloquent Repository

The concrete repository implementation using Eloquent:

<?php

namespace App\Infrastructure\Persistence;

use App\Domain\Entities\Booking;
use App\Domain\Repositories\BookingRepositoryInterface;
use App\Models\Booking as BookingModel;

class EloquentBookingRepository implements BookingRepositoryInterface
{
    public function save(Booking $booking): Booking
    {
        $model = BookingModel::create([
            'user_id' => $booking->userId,
            'service_id' => $booking->serviceId,
            'date' => $booking->date,
        ]);

        return new Booking($model->user_id, $model->service_id, $model->date);
    }
}
Enter fullscreen mode Exit fullscreen mode

Creating the Controller

The controller uses the use case to handle HTTP requests:

<?php

namespace App\Infrastructure\Http\Controllers;

use App\Application\UseCases\CreateBooking;
use Illuminate\Http\Request;
use Illuminate\Http\JsonResponse;
use use Illuminate\Validation\ValidationException;
use Throwable;

class BookingController
{
    public function __construct(private readonly CreateBooking $createBooking) {}

    public function store(Request $request): JsonResponse
    {
        $data = $request->validate([
            'user_id' => 'required|integer',
            'service_id' => 'required|integer',
            'date' => 'required|date',
        ]);

        try {
            $booking = $this->createBooking->execute($data);
            return response()->json(['message' => 'Booking successfully created', 'data' => $booking], 201);
        } catch (ValidationException $e) {
            return response()->json(['error' => $e->errors()], 422);
        }catch (Throwable $e) {
            return response()->json(['error' => $e->getMessage()], 400);
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Connecting Everything in Laravel

To inject the correct repository into the application, configure it in AppServiceProvider:

<?php

use App\Domain\Repositories\BookingRepositoryInterface;
use App\Infrastructure\Persistence\EloquentBookingRepository;

$this->app->bind(BookingRepositoryInterface::class, EloquentBookingRepository::class);
Enter fullscreen mode Exit fullscreen mode

Benefits of Clean Architecture in Laravel

  • Modular code: Facilitates technology and framework changes.

  • Easier testing: Use cases and entities can be tested in isolation.

  • Less coupling: Allows replacing the ORM, framework, or any layer without affecting the entire application.


Conclusion

Applying Clean Arch helps create more organized, modular, and maintainable systems. In this example, we structured a booking module by separating entities, use cases, repositories, and adapters.

By adopting this approach, you gain:

  • More structured and scalable projects.
  • Easier maintenance and testing.
  • Greater flexibility for integrating new technologies.

If you want to digging deeper into Clean Arch and laravel, here are some useful references:

Qodo Takeover

Introducing Qodo Gen 1.0: Transform Your Workflow with Agentic AI

While many AI coding tools operate as simple command-response systems, Qodo Gen 1.0 represents the next generation: autonomous, multi-step problem-solving agents that work alongside you.

Read full post

Top comments (0)

Sentry image

See why 4M developers consider Sentry, “not bad.”

Fixing code doesn’t have to be the worst part of your day. Learn how Sentry can help.

Learn more

👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay