Entity
Untuk memulai, pertama-tama kita perlu desain domain-nya. Komponen utama domain adalah entity dan value object. Bedanya, entity memiliki state, dan biasanya akan disimpan di database. Sedangkan value object tidak memiliki state.
Kita menggunakan paradigma DDD. Pada tahap ini kita tidak perlu memikirkan teknologi yang digunakan, seperti bagaimana menyimpan ke database dan sebagainya. Kita hanya perlu fokus pada model bisnisnya saja.
Entity Post
Berikut contoh entity untuk Post:
namespace App\Domain\Entity;
use Rekalogika\CommonBundle\Domain\Entity\AbstractAggregateRoot;
use Symfony\Component\Clock\DatePoint;
class Post extends AbstractAggregateRoot
{
private \DateTimeInterface $createdTime;
private string $title;
private string $content;
public function __construct(
string $title,
string $content,
) {
parent::__construct();
$this->title = $title;
$this->createdTime = new DatePoint();
}
}
Terlihat bahwa class ini sudah bisa mewakili fungsi dasar dari blog post. Setiap
blog post memiliki judul, konten, dan waktu pembuatan. Waktu pembuatan otomatis
dibuat saat objek dibuat. Sedangkan terlihat title dan content adalah
property yang wajib ada (tidak boleh kosong), dan harus ada saat objek pertama
kali dibuat.
DateTimeInterface adalah interface dari semua objek waktu di PHP.
Implementasi di PHP berupa DateTimeImmutable dan DateTime. Di sini kita
menggunakan DatePoint dari Symfony Clock. Alasannya karena DatePoint lebih
mudah diuji daripada DateTimeImmutable dan DateTime.
Properti createdTime tidak boleh diubah setelah, hanya bisa dilihat, maka kita
jadikan readonly dan hanya kita buatkan getternya, tidak perlu setter:
class Post extends AbstractAggregateRoot
{
private readonly \DateTimeInterface $createdTime;
public function getCreatedTime(): \DateTimeInterface
{
return $this->createdTime;
}
}
Objek DateTimeInterface adalah salah satu contoh dari value object.
Karena title dan content boleh diubah setelah pos dibuat, maka kita buatkan
getter dan setternya:
class Post extends AbstractAggregateRoot
{
public function getTitle(): string
{
return $this->title;
}
public function setTitle(string $title): void
{
$this->title = $title;
}
public function getContent(): string
{
return $this->content;
}
public function setContent(string $content): void
{
$this->content = $content;
}
}
Entity Comment
Karena setiap pos bisa memiliki komentar, maka kita buat entity komentar:
namespace App\Domain\Entity;
use Rekalogika\CommonBundle\Domain\Entity\AbstractEntity;
use Symfony\Component\Clock\DatePoint;
class Comment extends AbstractEntity
{
private string $content;
private readonly \DateTimeInterface $createdTime;
public function __construct(string $content)
{
parent::__construct();
$this->content = $content;
$this->createdTime = new DatePoint();
}
public function getCreatedTime(): \DateTimeInterface
{
return $this->createdTime;
}
}
Kurang lebih sama perilakunya dengan Post. Komentar memiliki konten dan waktu.
Relasi Pada Sisi Post
Karena setiap pos bisa memiliki beberapa komentar, maka kita perlu membuat
relasinya. Kita bisa membuat property comments di Post:
use Doctrine\Common\Collections\Collection;
use Rekalogika\Contracts\Collections\Recollection;
use Rekalogika\Domain\Collections\ArrayCollection;
use Rekalogika\Domain\Collections\RecollectionDecorator;
class Post extends AbstractAggregateRoot
{
/**
* @var Collection<string,Comment>
*/
private Collection $comments;
public function __construct(
// ...
) {
// ...
$this->comments = new ArrayCollection();
}
public function addComment(Comment $comment): void
{
$this->getComments()->add($comment);
$comment->setPost($this);
}
/**
* @return Recollection<string,Comment>
*/
public function getComments(): Recollection
{
return RecollectionDecorator::create(
collection: $this->insurables,
indexBy: 'id',
orderBy: ['id' => Order::Descending]
);
}
public function removeComment(Comment $comment): void
{
if ($this->getComments()->removeElement($comment)) {
if ($comment->getPost() === $this) {
$comment->setPost(null);
}
}
}
}
Yang perlu diperhatikan:
- Untuk menampung komentar, harus menggunakan tipe
Collection, tidak boleh array biasa. - Di atasnya kita tambahkan
@var Collection<string,Comment>untuk memberi tahu bahwacommentsadalah collection dari beberapa objekCommentdengan key bertipestring.
Kita menggunakan ArrayCollection versi kita, bukan yang dari Doctrine,
penjelasannya di
sini.
Untuk getComments(), kita menggunakan RecollectionDecorator, bukan plain
Collection. Alasannya:
- Mengimplementasikan
PageableInterfaceyang bisa digunakan langsung untuk batch processing dan pagination - Otomatis melakukan limit, sehingga tidak akan menghabiskan memori.
Cek info lengkapnya di sini.
Yang mengakses $this->comments hanyalah method getComments(). Method lain
harus menggunakan method getComments() untuk mengakses data komentar. Ini kita
lakukan untuk menghindari kesalahan pemrograman yang dapat mengakibatkan
out-of-memory.
Relasi Pada Sisi Comment
Lalu untuk Comment, kita bisa menambahkan property post:
class Comment extends AbstractEntity
{
private ?Post $post = null;
public function setPost(?Post $post): void
{
$this->post = $post;
}
public function getPost(): Post
{
return $this->post;
}
}
Jika $post null, maka komentar tersebut tidak terhubung dengan pos manapun.