Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: split model and entity #61

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

# https://docs.docker.com/engine/reference/builder/#understand-how-arg-and-from-interact
ARG PHP_VERSION=8.2
ARG CADDY_VERSION=2.7
ARG CADDY_VERSION=2.8

# "php" stage
FROM php:${PHP_VERSION}-fpm-alpine AS symfony_php
Expand Down
4 changes: 2 additions & 2 deletions config/packages/doctrine.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@
'BookStore' => [
'is_bundle' => false,
'type' => 'attribute',
'dir' => '%kernel.project_dir%/src/BookStore/Domain',
'prefix' => 'App\BookStore\Domain',
'dir' => '%kernel.project_dir%/src/BookStore/Infrastructure',
'prefix' => 'App\BookStore\Infrastructure',
],
'Shared' => [
'is_bundle' => false,
Expand Down
17 changes: 3 additions & 14 deletions src/BookStore/Domain/Model/Book.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,31 +11,20 @@
use App\BookStore\Domain\ValueObject\BookName;
use App\BookStore\Domain\ValueObject\Discount;
use App\BookStore\Domain\ValueObject\Price;
use Doctrine\ORM\Mapping as ORM;

#[ORM\Entity]
class Book
{
#[ORM\Embedded(columnPrefix: false)]
private readonly BookId $id;
private BookId $id;

public function __construct(
#[ORM\Embedded(columnPrefix: false)]
private BookName $name,

#[ORM\Embedded(columnPrefix: false)]
private BookDescription $description,

#[ORM\Embedded(columnPrefix: false)]
private Author $author,

#[ORM\Embedded(columnPrefix: false)]
private BookContent $content,

#[ORM\Embedded(columnPrefix: false)]
private Price $price,
?BookId $id = null,
) {
$this->id = new BookId();
$this->id = $id ?? new BookId();
}

public function update(
Expand Down
38 changes: 32 additions & 6 deletions src/BookStore/Infrastructure/Doctrine/DoctrineBookRepository.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
use App\BookStore\Domain\Repository\BookRepositoryInterface;
use App\BookStore\Domain\ValueObject\Author;
use App\BookStore\Domain\ValueObject\BookId;
use App\BookStore\Infrastructure\Doctrine\Entity\BookEntity;
use App\Shared\Infrastructure\Doctrine\DoctrineRepository;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\QueryBuilder;
Expand All @@ -17,7 +18,7 @@
*/
final class DoctrineBookRepository extends DoctrineRepository implements BookRepositoryInterface
{
private const ENTITY_CLASS = Book::class;
private const ENTITY_CLASS = BookEntity::class;
private const ALIAS = 'book';

public function __construct(EntityManagerInterface $em)
Expand All @@ -27,30 +28,55 @@ public function __construct(EntityManagerInterface $em)

public function add(Book $book): void
{
$this->em->persist($book);
/** @var ?BookEntity */
$entity = $this->em->find(self::ENTITY_CLASS, $book->id()->value);

if (null !== $entity) {
$entity->updateFromModel($book);
} else {
$entity = BookEntity::fromModel($book);
}

$this->em->persist($entity);
}

public function remove(Book $book): void
{
$this->em->remove($book);
/** @var ?BookEntity */
$entity = $this->em->find(self::ENTITY_CLASS, $book->id()->value);

if (null !== $entity) {
$this->em->remove($entity);
}
}

public function ofId(BookId $id): ?Book
{
return $this->em->find(self::ENTITY_CLASS, $id->value);
/** @var ?BookEntity */
$entity = $this->em->find(self::ENTITY_CLASS, $id->value);

return null !== $entity ? $entity->toModel() : null;
}

public function withAuthor(Author $author): static
{
return $this->filter(static function (QueryBuilder $qb) use ($author): void {
$qb->where(sprintf('%s.author.value = :author', self::ALIAS))->setParameter('author', $author->value);
$qb->where(sprintf('%s.author = :author', self::ALIAS))->setParameter('author', $author->value);
});
}

public function withCheapestsFirst(): static
{
return $this->filter(static function (QueryBuilder $qb): void {
$qb->orderBy(sprintf('%s.price.amount', self::ALIAS), 'ASC');
$qb->orderBy(sprintf('%s.price', self::ALIAS), 'ASC');
});
}

public function getIterator(): \Iterator
{
foreach (parent::getIterator() as $entity) {
/** @var BookEntity $entity */
yield $entity->toModel();
}
}
}
72 changes: 72 additions & 0 deletions src/BookStore/Infrastructure/Doctrine/Entity/BookEntity.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
<?php

declare(strict_types=1);

namespace App\BookStore\Infrastructure\Doctrine\Entity;

use App\BookStore\Domain\Model\Book;
use App\BookStore\Domain\ValueObject\Author;
use App\BookStore\Domain\ValueObject\BookContent;
use App\BookStore\Domain\ValueObject\BookDescription;
use App\BookStore\Domain\ValueObject\BookId;
use App\BookStore\Domain\ValueObject\BookName;
use App\BookStore\Domain\ValueObject\Price;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Uid\AbstractUid;

#[ORM\Entity]
#[ORM\Table(name: 'book')]
class BookEntity
{
public function __construct(
#[ORM\Id]
#[ORM\Column(type: 'uuid')]
private readonly AbstractUid $id,
#[ORM\Column(length: 255)]
private string $name,
#[ORM\Column(length: 1023)]
private string $description,
#[ORM\Column(length: 255)]
private string $author,
#[ORM\Column(length: 65535)]
private string $content,
#[ORM\Column(options: ['unsigned' => true])]
private int $price,
) {
}

public static function fromModel(Book $model): self
{
return new self(
id: $model->id()->value,
name: $model->name()->value,
description: $model->description()->value,
author: $model->author()->value,
content: $model->content()->value,
price: $model->price()->amount,
);
}

public function toModel(): Book
{
$book = new Book(
id: new BookId($this->id),
name: new BookName($this->name),
description: new BookDescription($this->description),
author: new Author($this->author),
content: new BookContent($this->content),
price: new Price($this->price),
);

return $book;
}

public function updateFromModel(Book $model): void
{
$this->name = $model->name()->value;
$this->description = $model->description()->value;
$this->author = $model->author()->value;
$this->content = $model->content()->value;
$this->price = $model->price()->amount;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

namespace App\Tests\BookStore\Integration\Doctrine;

use App\BookStore\Domain\Model\Book;
use App\BookStore\Domain\ValueObject\Author;
use App\BookStore\Infrastructure\Doctrine\DoctrineBookRepository;
use App\Shared\Infrastructure\Doctrine\DoctrinePaginator;
Expand Down Expand Up @@ -154,7 +155,7 @@ public function testIteratorWithoutPagination(): void

$i = 0;
foreach ($repository as $book) {
static::assertSame($books[$i], $book);
static::assertEquals($books[$i]->id(), $book->id());
++$i;
}
}
Expand All @@ -170,6 +171,7 @@ public function testIteratorWithPagination(): void
DummyBookFactory::createBook(),
DummyBookFactory::createBook(),
];
$bookIds = array_map(static fn (Book $b) => (string) $b->id(), $books);

foreach ($books as $book) {
$repository->add($book);
Expand All @@ -180,7 +182,7 @@ public function testIteratorWithPagination(): void

$i = 0;
foreach ($repository as $book) {
static::assertContains($book, $books);
static::assertContains((string) $book->id(), $bookIds);
++$i;
}

Expand All @@ -190,7 +192,7 @@ public function testIteratorWithPagination(): void

$i = 0;
foreach ($repository as $book) {
static::assertContains($book, $books);
static::assertContains((string) $book->id(), $bookIds);
++$i;
}

Expand Down