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\Blog\ValueObjects;
use Lava83\LaravelDdd\Domain\ValueObjects\Identity\Uuid;
final class ArticleId extends Uuid {}
declare(strict_types=1);
namespace App\Domain\Blog\ValueObjects;
use Lava83\LaravelDdd\Domain\Exceptions\ValidationException;
use Lava83\LaravelDdd\Domain\ValueObjects\ValueObject;
final class Title extends ValueObject
{
private function __construct(private readonly string $value) {}
/**
* @throws ValidationException
*/
public static function fromString(string $value): self
{
$value = trim($value);
if ($value === '' || mb_strlen($value) > 255) {
throw new ValidationException('Title must be between 1 and 255 characters.');
}
return new self($value);
}
public function value(): string
{
return $this->value;
}
public function equals(self $other): bool
{
return $this->value === $other->value;
}
public function jsonSerialize(): string
{
return $this->value;
}
public function __toString(): string
{
return $this->value;
}
}
declare(strict_types=1);
namespace App\Domain\Blog;
use App\Domain\Blog\ValueObjects\ArticleId;
use App\Domain\Blog\ValueObjects\Title;
use Lava83\LaravelDdd\Domain\Entities\Aggregate;
use Lava83\LaravelDdd\Infrastructure\Models\Model;
final class Article extends Aggregate
{
public function __construct(
private readonly ArticleId $id,
private Title $title,
) {
parent::__construct();
}
public static function create(ArticleId $id, Title $title): self
{
return new self($id, $title);
}
public function id(): ArticleId
{
return $this->id;
}
public function title(): Title
{
return $this->title;
}
public function rename(Title $title): void
{
$this->updateAggregateRoot(['title' => $title]);
}
/**
* Rebuild the aggregate from its persisted state.
*
* @deprecated Prefer the mapper for hydration. The base Entity still
* declares this as an abstract hook, so it is implemented
* here for completeness.
*/
public static function fromState(Model $state): static
{
/** @var \App\Infrastructure\Models\ArticleModel $state */
return new self(
ArticleId::fromString((string) $state->id),
Title::fromString((string) $state->title),
);
}
}
declare(strict_types=1);
namespace App\Infrastructure\Models;
use App\Domain\Blog\Article;
use Lava83\LaravelDdd\Infrastructure\Models\Concerns\HasUuids;
use Lava83\LaravelDdd\Infrastructure\Models\Model;
/**
* @property string $id
* @property string $title
*/
final class ArticleModel extends Model
{
use HasUuids;
protected $table = 'articles';
/** @var class-string<Article> */
protected ?string $entityClassName = Article::class;
/** @var list<string> */
protected $fillable = ['title'];
}
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::create('articles', function (Blueprint $table): void {
$table->uuid('id')->primary();
$table->string('title');
$table->unsignedInteger('version')->default(1);
$table->timestamps();
});
}
public function down(): void
{
Schema::dropIfExists('articles');
}
};
declare(strict_types=1);
namespace App\Infrastructure\Mappers;
use App\Domain\Blog\Article;
use App\Infrastructure\Models\ArticleModel;
use Illuminate\Database\Eloquent\Model as EloquentModel;
use Lava83\LaravelDdd\Domain\Entities\Entity;
use Lava83\LaravelDdd\Infrastructure\Contracts\EntityMapper;
use Lava83\LaravelDdd\Infrastructure\Mappers\EntityMapper as BaseMapper;
/**
* @implements EntityMapper<Article, ArticleModel>
*/
final class ArticleMapper extends BaseMapper implements EntityMapper
{
/**
* @param ArticleModel $model
*/
public static function toEntity(EloquentModel $model, bool $deep = false): Article
{
/** @var ArticleModel $model */
$article = Article::fromState($model);
// Restore version and timestamps from the persisted row.
$article->hydrate($model);
return $article;
}
/**
* @param Article $entity
*/
public static function toModel(Entity $entity): ArticleModel
{
return self::findOrCreateModelFillData($entity, ArticleModel::class, [
'title' => (string) $entity->title(),
]);
}
}
declare(strict_types=1);
namespace App\Domain\Blog\Contracts;
use App\Domain\Blog\Article;
use App\Domain\Blog\ValueObjects\ArticleId;
use Lava83\LaravelDdd\Domain\Contracts\Repository;
interface ArticleRepository extends Repository
{
public function nextId(): ArticleId;
public function save(Article $article): void;
public function find(ArticleId $id): ?Article;
public function findOrFail(ArticleId $id): Article;
}
declare(strict_types=1);
namespace App\Infrastructure\Repositories;
use App\Domain\Blog\Article;
use App\Domain\Blog\Contracts\ArticleRepository;
use App\Domain\Blog\ValueObjects\ArticleId;
use App\Infrastructure\Models\ArticleModel;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\DB;
use Lava83\LaravelDdd\Domain\ValueObjects\Identity\Uuid;
use Lava83\LaravelDdd\Infrastructure\Repositories\Repository;
/**
* @extends Repository<ArticleModel, Article>
*/
final class EloquentArticleRepository extends Repository implements ArticleRepository
{
/** @var class-string<Article> */
protected ?string $entityClassName = Article::class;
public function nextId(): ArticleId
{
return ArticleId::generate();
}
public function save(Article $article): void
{
DB::transaction(fn () => $this->saveEntity($article));
}
public function find(ArticleId $id): ?Article
{
return ArticleModel::query()->find($id->value())?->toEntity();
}
public function findOrFail(ArticleId $id): Article
{
return ArticleModel::query()->findOrFail($id->value())->toEntity();
}
public function exists(Uuid $id): bool
{
return ArticleModel::query()->whereKey($id->value())->exists();
}
public function delete(Uuid $id): void
{
$model = ArticleModel::query()->find($id->value());
if ($model !== null) {
$this->deleteEntity($model->toEntity());
}
}
/**
* @return Collection<int, Article>
*/
public function all(): Collection
{
return ArticleModel::query()
->get()
->map(fn (ArticleModel $model): Article => $model->toEntity());
}
public function count(): int
{
return ArticleModel::query()->count();
}
}
declare(strict_types=1);
namespace App\Providers;
use App\Domain\Blog\Article;
use App\Domain\Blog\Contracts\ArticleRepository;
use App\Infrastructure\Mappers\ArticleMapper;
use App\Infrastructure\Repositories\EloquentArticleRepository;
use Illuminate\Support\ServiceProvider;
final class BlogServiceProvider extends ServiceProvider
{
public function register(): void
{
$this->app->bind(ArticleRepository::class, EloquentArticleRepository::class);
}
public function boot(): void
{
entity_mapper_resolver()->registerMapper(Article::class, new ArticleMapper());
}
}
use App\Domain\Blog\Article;
use App\Domain\Blog\Contracts\ArticleRepository;
use App\Domain\Blog\ValueObjects\Title;
$repository = app(ArticleRepository::class);
// Create and persist a new article.
$article = Article::create($repository->nextId(), Title::fromString('Hello, DDD'));
$repository->save($article);
// Load it, change it through the domain, persist again.
$loaded = $repository->findOrFail($article->id());
$loaded->rename(Title::fromString('Hello, Domain-Driven Design'));
$repository->save($loaded); // version is bumped; optimistic locking guards concurrent writes
use Lava83\LaravelDdd\Infrastructure\Models\Filter\Builder;
$builder = Builder::make()
->like('title', 'DDD')
->eq('status', 'published')
->in('category', ['laravel', 'php'])
->gte('reading_time', 5)
->isNull('archived_at');
// `filter()` is a query scope from the Filterable trait; each filter must be
// permitted by the model's allowedFilters() list.
$articles = ArticleModel::query()->filter($builder->toArray())->get();