1. Go to this page and download the library: Download lava83/laravel-ddd library. Choose the download type require.
2. Extract the ZIP file and open the index.php.
3. Add this code to the index.php.
<?php
require_once('vendor/autoload.php');
/* Start to develop here. Best regards https://php-download.com/ */
lava83 / laravel-ddd example snippets
declare(strict_types=1);
namespace App\Domain\TrelloManagement\Entities;
use Illuminate\Support\Collection;
use Lava83\DddFoundation\Domain\Entities\Entity;
use App\Domain\TrelloManagement\ValueObjects\Identity\MemberId;
class Member extends Entity
{
public function __construct(
private MemberId $trelloId,
protected string $fullName,
protected string $username,
) {
parent::__construct();
}
public function id(): MemberId
{
return $this->trelloId;
}
public function fullName(): string
{
return $this->fullName;
}
public function username(): string
{
return $this->username;
}
public function update(string $fullName, string $username): void
{
$this->updateEntity([
'fullName' => $fullName,
'username' => $username,
]);
}
protected function applyChanges(Collection $changes): void
{
$this->applyChangesByPropertyMap([
'fullName' => fn($value) => $this->fullName = $value,
'username' => fn($value) => $this->username = $value,
], $changes);
}
}
declare(strict_types=1);
namespace App\Infrastructure\Repositories\Trello;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\DB;
use Lava83\DddFoundation\Infrastructure\Repositories\Repository;
use App\Domain\TrelloManagement\Contracts\Board\BoardRepositoryInterface;
use App\Domain\TrelloManagement\Entities\Board;
use App\Domain\TrelloManagement\ValueObjects\Identity\BoardId;
use App\Infrastructure\Mappers\Trello\BoardMapper;
use App\Infrastructure\Models\Trello\Board\BoardModel;
class EloquentBoardRepository extends Repository implements BoardRepositoryInterface
{
protected string $aggregateClass = Board::class;
public function exists(BoardId $boardId): bool
{
return BoardModel::where('trello_id', $boardId->toString())->exists();
}
public function find(BoardId $boardId): ?Board
{
$model = BoardModel::with(['boardLists', 'webhook'])
->find($boardId->toString());
return $model ? BoardMapper::toEntity($model, true) : null;
}
public function findOrFail(BoardId $boardId): Board
{
return BoardMapper::toEntity(
BoardModel::with(['boardLists', 'webhook'])
->findOrFail($boardId->toString()),
true
);
}
public function findAll(): Collection
{
return BoardModel::with(['boardLists', 'webhook'])
->latest()
->get()
->map(fn(BoardModel $model) => BoardMapper::toEntity($model, true));
}
public function save(Board $board): void
{
DB::transaction(fn() => $this->saveEntity($board));
}
public function delete(Board $board): void
{
// Implementation
}
}
declare(strict_types=1);
namespace App\Infrastructure\Mappers\Trello;
use Lava83\DddFoundation\Domain\Entities\Entity;
use Lava83\DddFoundation\Infrastructure\Contracts\EntityMapper;
use Lava83\DddFoundation\Infrastructure\Models\Model;
use App\Domain\TrelloManagement\Entities\Member;
use App\Domain\TrelloManagement\ValueObjects\Identity\MemberId;
use App\Infrastructure\Models\Trello\Member\MemberModel;
class MemberMapper implements EntityMapper
{
/**
* @param MemberModel $model
*/
public static function toEntity(Model $model, bool $deep = false): Entity
{
$member = new Member(
trelloId: MemberId::fromString($model->trello_id),
fullName: $model->full_name,
username: $model->username,
);
$member->hydrate($model);
return $member;
}
/**
* @param Member $entity
*/
public static function toModel(Entity $entity): MemberModel
{
$data = [
'trello_id' => $entity->trelloId(),
'full_name' => $entity->fullName(),
'username' => $entity->username(),
'created_at' => $entity->createdAt(),
'updated_at' => $entity->updatedAt(),
'version' => $entity->version(),
];
$member = MemberModel::findOr(
$entity->id(),
['*'],
fn() => app(MemberModel::class)
);
$member->fill($data);
return $member;
}
}
declare(strict_types=1);
namespace App\Infrastructure\Mappers;
use Lava83\DddFoundation\Domain\Entities\Aggregate;
use Lava83\DddFoundation\Infrastructure\Contracts\EntityMapper;
use Lava83\DddFoundation\Infrastructure\Contracts\EntityMapperResolver as EntityMapperResolverContract;
use App\Domain\TrelloManagement\Entities\Board;
use App\Domain\TrelloManagement\Entities\Member;
use App\Infrastructure\Mappers\Trello\BoardMapper;
use App\Infrastructure\Mappers\Trello\MemberMapper;
class EntityMapperResolver implements EntityMapperResolverContract
{
/**
* @param class-string<Aggregate> $entityClass
*/
public function resolve(string $entityClass): EntityMapper
{
return match ($entityClass) {
Board::class => app(BoardMapper::class),
Member::class => app(MemberMapper::class),
default => throw new NoMapperFoundForEntity($entityClass),
};
}
}
declare(strict_types=1);
namespace App\Domain\TrelloManagement\Events;
use Lava83\DddFoundation\Domain\Events\DomainEvent;
class BoardCreated extends DomainEvent
{
public function eventName(): string
{
return 'trello.board.created';
}
}
declare(strict_types=1);
namespace App\Application\Listeners;
use Illuminate\Events\Dispatcher;
use App\Domain\TrelloManagement\Events\BoardCreated;
use App\Domain\TrelloManagement\Events\BoardUpdated;
class BoardEventSubscriber
{
public function handleBoardCreated(BoardCreated $event): void
{
// Handle board creation
// e.g., send notifications, update read models, etc.
}
public function handleBoardUpdated(BoardUpdated $event): void
{
// Handle board updates
}
public function subscribe(Dispatcher $events): array
{
return [
BoardCreated::class => 'handleBoardCreated',
BoardUpdated::class => 'handleBoardUpdated',
];
}
}
declare(strict_types=1);
namespace App\Application\Services\Trello\Board;
use Illuminate\Support\Collection;
use Lava83\DddFoundation\Domain\ValueObjects\Communication\Link;
use App\Domain\TrelloManagement\Contracts\Board\BoardRepositoryInterface;
use App\Domain\TrelloManagement\Contracts\Board\BoardServiceInterface;
use App\Domain\TrelloManagement\Entities\Board;
use App\Domain\TrelloManagement\ValueObjects\Identity\BoardId;
class BoardApplicationService implements BoardServiceInterface
{
public function __construct(
private BoardRepositoryInterface $boardRepository,
) {}
public function listBoards(): Collection
{
return $this->boardRepository->findAll();
}
public function board(string $boardId): Board
{
return $this->boardRepository->findOrFail(
BoardId::fromString($boardId)
);
}
public function createBoard(array $data): Board
{
$board = Board::create(
trelloId: BoardId::fromString($data['trello_id']),
name: $data['name'],
description: $data['description'],
isClosed: $data['is_closed'],
link: Link::fromString($data['link']),
shortUrl: Link::fromString($data['short_url']),
isSubscribed: $data['is_subscribed'],
closedAt: $data['closed_at'] ?? null,
lastActivityAt: $data['last_activity_at'] ?? null,
lastView: $data['last_view'] ?? null,
);
$this->boardRepository->save($board);
return $board;
}
public function updateBoard(string $boardId, array $data): Board
{
$board = $this->boardRepository->findOrFail(
BoardId::fromString($boardId)
);
$board->update(
name: $data['name'],
description: $data['description'],
isClosed: $data['is_closed'],
link: Link::fromString($data['link']),
shortUrl: Link::fromString($data['short_url']),
isSubscribed: $data['is_subscribed'],
closedAt: $data['closed_at'] ?? null,
lastActivityAt: $data['last_activity_at'] ?? null,
lastView: $data['last_view'] ?? null,
);
$this->boardRepository->save($board);
return $board;
}
}
declare(strict_types=1);
namespace App\Domain\Shared\ValueObjects;
use Carbon\CarbonImmutable;
use Lava83\DddFoundation\Domain\ValueObjects\ValueObject;
class DateRange extends ValueObject
{
private function __construct(
private CarbonImmutable $startDate,
private CarbonImmutable $endDate,
) {
$this->validate();
}
public static function create(
CarbonImmutable $startDate,
CarbonImmutable $endDate
): self {
return new self($startDate, $endDate);
}
public function startDate(): CarbonImmutable
{
return $this->startDate;
}
public function endDate(): CarbonImmutable
{
return $this->endDate;
}
public function contains(CarbonImmutable $date): bool
{
return $date->greaterThanOrEqualTo($this->startDate)
&& $date->lessThanOrEqualTo($this->endDate);
}
public function overlaps(DateRange $other): bool
{
return $this->startDate->lessThanOrEqualTo($other->endDate)
&& $other->startDate->lessThanOrEqualTo($this->endDate);
}
public function durationInDays(): int
{
return $this->startDate->diffInDays($this->endDate);
}
protected function validate(): void
{
if ($this->startDate->greaterThan($this->endDate)) {
throw new \InvalidArgumentException(
'Start date must be before or equal to end date'
);
}
}
public function toString(): string
{
return sprintf(
'%s to %s',
$this->startDate->format('Y-m-d'),
$this->endDate->format('Y-m-d')
);
}
public function toArray(): array
{
return [
'start_date' => $this->startDate->toDateString(),
'end_date' => $this->endDate->toDateString(),
];
}
public function equals(mixed $other): bool
{
if (!$other instanceof self) {
return false;
}
return $this->startDate->equalTo($other->startDate)
&& $this->endDate->equalTo($other->endDate);
}
}
use Lava83\DddFoundation\Domain\ValueObjects\Identity\Uuid;
use Lava83\DddFoundation\Domain\ValueObjects\Identity\MongoObjectId;
use Lava83\DddFoundation\Domain\ValueObjects\Communication\Email;
use Lava83\DddFoundation\Domain\ValueObjects\Communication\Link;
use Lava83\DddFoundation\Domain\ValueObjects\Data\Json;
// UUID
$userId = Uuid::generate();
$userId = Uuid::fromString('550e8400-e29b-41d4-a716-446655440000');
// MongoDB ObjectId (compatible with Trello IDs)
$boardId = MongoObjectId::fromString('507f1f77bcf86cd799439011');
// Email
$email = Email::fromString('[email protected]');
echo $email->toString(); // [email protected]
// Link/URL
$url = Link::fromString('https://example.com');
echo $url->toString(); // https://example.com
// JSON Data
$json = Json::fromArray(['key' => 'value']);
$data = $json->toArray();
$board = Board::create(/* ... */);
$board->update(/* ... */); // Records BoardUpdated event
$boardRepository->save($board);
// 1. Saves to database
// 2. Automatically dispatches all uncommitted events via Laravel's event system
// 3. Clears uncommitted events from the aggregate
// User A loads board
$boardA = $boardRepository->find($boardId);
$versionA = $boardA->version(); // version = 1
// User B loads same board
$boardB = $boardRepository->find($boardId);
// User B updates and saves
$boardB->update(/* ... */);
$boardRepository->save($boardB); // version now = 2
// User A tries to save
$boardA->update(/* ... */);
$boardRepository->save($boardA);
// Throws ConcurrencyException because version mismatch
public function save(Board $board): void
{
DB::transaction(fn() => $this->saveEntity($board));
}
namespace App\Infrastructure\Providers;
use Illuminate\Support\ServiceProvider;
use App\Domain\TrelloManagement\Contracts\Board\BoardRepositoryInterface;
use App\Infrastructure\Repositories\Trello\EloquentBoardRepository;
class RepositoriesServiceProvider extends ServiceProvider
{
public function register(): void
{
$this->app->bind(
BoardRepositoryInterface::class,
EloquentBoardRepository::class
);
// Register other repositories...
}
}
namespace App\Application\Providers;
use Illuminate\Support\ServiceProvider;
use App\Domain\TrelloManagement\Contracts\Board\BoardServiceInterface;
use App\Application\Services\Trello\Board\BoardApplicationService;
class ApplicationServiceProvider extends ServiceProvider
{
public function register(): void
{
$this->app->bind(
BoardServiceInterface::class,
BoardApplicationService::class
);
// Register other services...
}
}
// ✅ Good - Uses generic Collection from illuminate/support
use Illuminate\Support\Collection;
class Board extends Aggregate
{
public function __construct(
// ...
protected Collection $lists,
) {
parent::__construct();
}
}
// ❌ Avoid - Don't use Eloquent in domain layer
use Illuminate\Database\Eloquent\Collection;
public function activeBoards(): Collection
{
return collect($this->boards)->filter(
fn(Board $board) => !$board->isClosed()
);
}
// ✅ Good - Clear intent
$board = Board::create($id, $name, $description, ...);
// ❌ Avoid - Using new directly
$board = new Board($id, $name, $description, ...);
public function update(/* params */): void
{
$this->updateAggregateRoot(
[/* changes */],
BoardUpdated::class, // Always provide event class
);
}
public function save(Board $board): void
{
DB::transaction(fn() => $this->saveEntity($board));
}
public function resolve(string $entityClass): EntityMapper
{
return match ($entityClass) {
YourEntity::class => app(YourEntityMapper::class),
// ...
};
}
try {
$repository->save($board);
} catch (ConcurrencyException $e) {
// Reload entity and retry, or notify user
$board = $repository->findOrFail($boardId);
// Reapply changes...
}
Loading please wait ...
Before you can download the PHP files, the dependencies should be resolved. This can take some minutes. Please be patient.