| name | acc-create-entity |
| description | Generates DDD Entities for PHP 8.5. Creates identity-based objects with behavior, state transitions, and invariant protection. Includes unit tests. |
Entity Generator
Generate DDD-compliant Entities with identity, behavior, and tests.
Entity Characteristics
- Identity: Has unique identifier (ID)
- Lifecycle: Created, modified, potentially deleted
- Behavior: Contains domain logic (not just data)
- Invariants: Protects business rules
- State transitions: Controlled mutations
- No public setters: State changed via behavior methods
Template
<?php
declare(strict_types=1);
namespace Domain\{BoundedContext}\Entity;
use Domain\{BoundedContext}\ValueObject\{Name}Id;
use Domain\{BoundedContext}\Enum\{Name}Status;
use Domain\{BoundedContext}\Exception\{Exceptions};
final class {Name}
{
private {Name}Status $status;
private DateTimeImmutable $createdAt;
private ?DateTimeImmutable $updatedAt = null;
public function __construct(
private readonly {Name}Id $id,
{constructorProperties}
) {
{constructorValidation}
$this->status = {Name}Status::default();
$this->createdAt = new DateTimeImmutable();
}
public function id(): {Name}Id
{
return $this->id;
}
public function status(): {Name}Status
{
return $this->status;
}
{behaviorMethods}
private function touch(): void
{
$this->updatedAt = new DateTimeImmutable();
}
}
Test Template
<?php
declare(strict_types=1);
namespace Tests\Unit\Domain\{BoundedContext}\Entity;
use Domain\{BoundedContext}\Entity\{Name};
use Domain\{BoundedContext}\ValueObject\{Name}Id;
use Domain\{BoundedContext}\Enum\{Name}Status;
use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\Attributes\Group;
use PHPUnit\Framework\TestCase;
#[Group('unit')]
#[CoversClass({Name}::class)]
final class {Name}Test extends TestCase
{
public function testCreatesWithValidData(): void
{
= ->();
::({Name}::, ->());
::({Name}::(), ->());
}
{behaviorTests}
{Name}
{
{Name}(
id: {Name}::(),
{testConstructorArgs}
);
}
}
Common Entity Patterns
Order Entity
<?php
declare(strict_types=1);
namespace Domain\Order\Entity;
use Domain\Order\ValueObject\OrderId;
use Domain\Order\ValueObject\CustomerId;
use Domain\Order\ValueObject\Money;
use Domain\Order\Enum\OrderStatus;
use Domain\Order\Exception\CannotModifyConfirmedOrderException;
use Domain\Order\Exception\CannotConfirmEmptyOrderException;
use Domain\Order\Exception\InvalidStateTransitionException;
final class Order
{
private OrderStatus $status;
private array $lines = [];
private DateTimeImmutable $createdAt;
?DateTimeImmutable = ;
{
->status = ::;
->createdAt = ();
}
{
->id;
}
{
->customerId;
}
{
->status;
}
{
(->status !== ::) {
(->id);
}
->lines[] = (
product: ,
quantity: ,
unitPrice: ->()
);
}
{
(->status !== ::) {
(->id);
}
(!(->lines[])) {
;
}
(->lines[]);
->lines = (->lines);
}
{
(->status !== ::) {
(
->status,
::
);
}
((->lines)) {
(->id);
}
->status = ::;
->confirmedAt = ();
}
{
(!->status->()) {
(
->status,
::
);
}
->status = ::;
}
{
(
->lines,
fn (Money , OrderLine ) => ->(->()),
::()
);
}
{
->lines;
}
{
(->lines);
}
{
(->lines);
}
{
->createdAt;
}
{
->confirmedAt;
}
}
User Entity
<?php
declare(strict_types=1);
namespace Domain\User\Entity;
use Domain\User\ValueObject\UserId;
use Domain\User\ValueObject\Email;
use Domain\User\ValueObject\HashedPassword;
use Domain\User\Enum\UserStatus;
use Domain\User\Exception\UserAlreadyActivatedException;
use Domain\User\Exception\UserDeactivatedException;
final class User
{
private UserStatus $status;
private DateTimeImmutable $createdAt;
private ?DateTimeImmutable $lastLoginAt = null;
public function __construct(
private readonly UserId $id,
Email ,
HashedPassword ,
) {
((())) {
();
}
->status = ::;
->createdAt = ();
}
{
->id;
}
{
->email;
}
{
->name;
}
{
->status;
}
{
(->status === ::) {
(->id);
}
->status = ::;
}
{
->status = ::;
}
{
->();
->email = ;
}
{
->();
->password = ;
}
{
->();
((())) {
();
}
->name = ;
}
{
->();
->lastLoginAt = ();
}
{
->(->password, );
}
{
->status === ::;
}
{
(->status === ::) {
(->id);
}
}
}
Entity Design Principles
1. Behavior Over Data
class Order
{
public function setStatus(string $status): void
{
$this->status = $status;
}
}
class Order
{
public function confirm(): void
{
if (!$this->canBeConfirmed()) {
throw new InvalidStateTransitionException();
}
$this->status = OrderStatus::Confirmed;
$this->confirmedAt = new DateTimeImmutable();
}
private function canBeConfirmed(): bool
{
return $this->status === OrderStatus::Draft && !empty($this->lines);
}
}
2. Invariant Protection
public function addLine(Product $product, int $quantity): void
{
if ($this->status !== OrderStatus::Draft) {
throw new CannotModifyConfirmedOrderException();
}
if ($quantity <= 0) {
throw new InvalidQuantityException();
}
$this->lines[] = new OrderLine($product, $quantity);
}
3. State Transitions
enum OrderStatus: string
{
case Draft = 'draft';
case Confirmed = 'confirmed';
case Paid = 'paid';
case Shipped = 'shipped';
case Cancelled = 'cancelled';
public function canTransitionTo(self $target): bool
{
return match($this) {
self::Draft => in_array($target, [self::Confirmed, self::Cancelled]),
self::Confirmed => in_array($target, [self::Paid, self::Cancelled]),
self::Paid => in_array($target, [self::Shipped, self::Cancelled]),
self::Shipped => false,
:: => ,
};
}
{
->(::);
}
}
Generation Instructions
When asked to create an Entity:
- Identify the identity (what makes it unique)
- Define the lifecycle (statuses/states)
- List invariants (business rules to protect)
- Design behavior methods (what it can do)
- Generate tests for behavior and invariants
Naming Conventions
| Concept | Method Pattern | Exception |
|---|
| State change | confirm(), activate(), cancel() | InvalidStateTransitionException |
| Add relation | addLine(), addItem() | CannotModifyException |
| Update property | changeEmail(), updateName() | InvalidValueException |
| Query state | isActive(), canBeConfirmed() | N/A (boolean return) |
Usage
To generate an Entity, provide:
- Name (e.g., "Order", "User")
- Bounded Context (e.g., "Order", "User")
- Identity type (e.g., "OrderId")
- States/Statuses
- Key behaviors needed
- Invariants to protect