7 min readCalm pace · scan the outline anytime

Laravel persistence (Eloquent)

Eloquent stays in Infrastructure — repositories map OrderEntity to OrderModel manually; repository-only Eloquent access.

Persistence (Eloquent)

Eloquent is the default Laravel persistence driver. It belongs only in Infrastructure repositories (and related models/factories). Application and Domain speak Entities. Examples use greenfield Ordering.

Folders

Ordering/Infrastructure/Persistence/Eloquent/
├── Models/OrderModel.php
├── Repositories/OrderRepository.php
├── Factories/…          # optional
└── Migrations/…         # see Migrations page

Domain port:

Ordering/Domain/Ports/Order/OrderRepositoryInterface.php

Layer responsibilities

ComponentLayerPurpose
Port (interface)DomainContract (findById, save, …)
ImplementationInfrastructureEloquent / SQL
Entity mappingInfrastructureModel ↔ Entity

Domain port

namespace App\Modules\Ordering\Domain\Ports\Order;

use App\Modules\Ordering\Domain\Entities\OrderEntity;

interface OrderRepositoryInterface
{
    public function findById(int $id): ?OrderEntity;

    public function save(OrderEntity $entity): void;
}

Infrastructure adapter

namespace App\Modules\Ordering\Infrastructure\Persistence\Eloquent\Repositories;

use App\Modules\Ordering\Domain\Entities\OrderEntity;
use App\Modules\Ordering\Domain\Ports\Order\OrderRepositoryInterface;
use App\Modules\Ordering\Infrastructure\Persistence\Eloquent\Models\OrderModel;

class OrderRepository implements OrderRepositoryInterface
{
    public function findById(int $id): ?OrderEntity
    {
        $model = OrderModel::find($id);

        if (! $model) {
            return null;
        }

        return new OrderEntity(
            id: $model->getId(),
            status: $model->getStatus(),
            amount: $model->getAmount(),
        );
    }

    public function save(OrderEntity $entity): void
    {
        OrderModel::updateOrCreate(
            ['id' => $entity->id],
            [
                'status' => $entity->status,
                'amount' => $entity->amount,
            ],
        );
    }
}

Read models via getters; write via setters / explicit attribute arrays. Direct $model->attribute access on Eloquent models is forbidden in host standards that enforce encapsulation.

Eloquent access is repository-only

The Repository (and its base class) is the only place allowed to touch Eloquent models directly. Use Cases, Domain Services, and other Infrastructure services must obtain data through a Repository.

// ❌ WRONG — Infrastructure helper reaching for the model statically
$model = OrderModel::find($orderId);

// ✅ CORRECT — go through the Repository (Domain Port from Application)
$entity = $this->orders->findById($orderId);
  • Domain / Application always depend on the Port and receive Entities.
  • Infrastructure services that genuinely need the persistence model (search index, reporting) may depend on the concrete Repository and a dedicated findModel()-style method kept off the Domain Port.
  • Forbidden outside the Repository: Model::find(), Model::where(), Model::create(), Model::query(), Model::updateOrCreate(), and similar.

Why manual mapping is required

ReasonEffect
Schema decouplingRename a column → touch Repository only
Strict business rulesUse Cases operate on Entities, not rows
EncapsulationSensitive columns never leak via Entity

Model pattern (Laravel host)

Eloquent models live under Infrastructure/Persistence/Eloquent/Models/. They are not Domain Entities. Prefer:

  • Getters / setters for every persisted field used by mapping
  • No business policy methods that belong on Entities
  • Factories colocated for tests

Anti-patterns

Anti-patternFix
OrderEntity extends ModelPlain Entity + Infrastructure Model
Use Case calls OrderModel::query()Inject OrderRepositoryInterface
Repository returns paginator of ModelsMap items to Entities
Cross-module Eloquent relations for business joinsACL / Events

Golden line

UIUseCasePortEloquent RepositoryDomain Entity

Core reference: Ports & persistence.

Modular Hexagonal Domain-Driven Design
Core 1.0.0-draft