acc-create-entity
Generates DDD Entities for PHP 8.5. Creates identity-based objects with behavior, state transitions, and invariant protection. Includes unit tests.
Best use case
acc-create-entity is best used when you need a repeatable AI agent workflow instead of a one-off prompt.
Generates DDD Entities for PHP 8.5. Creates identity-based objects with behavior, state transitions, and invariant protection. Includes unit tests.
Teams using acc-create-entity should expect a more consistent output, faster repeated execution, less prompt rewriting.
When to use this skill
- You want a reusable workflow that can be run more than once with consistent structure.
When not to use this skill
- You only need a quick one-off answer and do not need a reusable workflow.
- You cannot install or maintain the underlying files, dependencies, or repository context.
Installation
Claude Code / Cursor / Codex
Manual Installation
- Download SKILL.md from GitHub
- Place it in
.claude/skills/acc-create-entity/SKILL.mdinside your project - Restart your AI agent — it will auto-discover the skill
How acc-create-entity Compares
| Feature / Agent | acc-create-entity | Standard Approach |
|---|---|---|
| Platform Support | Not specified | Limited / Varies |
| Context Awareness | High | Baseline |
| Installation Complexity | Unknown | N/A |
Frequently Asked Questions
What does this skill do?
Generates DDD Entities for PHP 8.5. Creates identity-based objects with behavior, state transitions, and invariant protection. Includes unit tests.
Where can I find the source code?
You can find the source code on GitHub using the link provided at the top of the page.
SKILL.md Source
# 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
<?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
<?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
{
$entity = $this->createEntity();
self::assertInstanceOf({Name}Id::class, $entity->id());
self::assertSame({Name}Status::default(), $entity->status());
}
{behaviorTests}
private function createEntity(): {Name}
{
return new {Name}(
id: {Name}Id::generate(),
{testConstructorArgs}
);
}
}
```
## Common Entity Patterns
### Order Entity
```php
<?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;
/** @var array<OrderLine> */
private array $lines = [];
private DateTimeImmutable $createdAt;
private ?DateTimeImmutable $confirmedAt = null;
public function __construct(
private readonly OrderId $id,
private readonly CustomerId $customerId
) {
$this->status = OrderStatus::Draft;
$this->createdAt = new DateTimeImmutable();
}
public function id(): OrderId
{
return $this->id;
}
public function customerId(): CustomerId
{
return $this->customerId;
}
public function status(): OrderStatus
{
return $this->status;
}
public function addLine(Product $product, int $quantity): void
{
if ($this->status !== OrderStatus::Draft) {
throw new CannotModifyConfirmedOrderException($this->id);
}
$this->lines[] = new OrderLine(
product: $product,
quantity: $quantity,
unitPrice: $product->price()
);
}
public function removeLine(int $index): void
{
if ($this->status !== OrderStatus::Draft) {
throw new CannotModifyConfirmedOrderException($this->id);
}
if (!isset($this->lines[$index])) {
return;
}
unset($this->lines[$index]);
$this->lines = array_values($this->lines);
}
public function confirm(): void
{
if ($this->status !== OrderStatus::Draft) {
throw new InvalidStateTransitionException(
$this->status,
OrderStatus::Confirmed
);
}
if (empty($this->lines)) {
throw new CannotConfirmEmptyOrderException($this->id);
}
$this->status = OrderStatus::Confirmed;
$this->confirmedAt = new DateTimeImmutable();
}
public function cancel(): void
{
if (!$this->status->canBeCancelled()) {
throw new InvalidStateTransitionException(
$this->status,
OrderStatus::Cancelled
);
}
$this->status = OrderStatus::Cancelled;
}
public function total(): Money
{
return array_reduce(
$this->lines,
fn (Money $carry, OrderLine $line) => $carry->add($line->total()),
Money::zero('USD')
);
}
/**
* @return array<OrderLine>
*/
public function lines(): array
{
return $this->lines;
}
public function lineCount(): int
{
return count($this->lines);
}
public function isEmpty(): bool
{
return empty($this->lines);
}
public function createdAt(): DateTimeImmutable
{
return $this->createdAt;
}
public function confirmedAt(): ?DateTimeImmutable
{
return $this->confirmedAt;
}
}
```
### User Entity
```php
<?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,
private Email $email,
private HashedPassword $password,
private string $name
) {
if (empty(trim($name))) {
throw new InvalidUserNameException();
}
$this->status = UserStatus::Pending;
$this->createdAt = new DateTimeImmutable();
}
public function id(): UserId
{
return $this->id;
}
public function email(): Email
{
return $this->email;
}
public function name(): string
{
return $this->name;
}
public function status(): UserStatus
{
return $this->status;
}
public function activate(): void
{
if ($this->status === UserStatus::Active) {
throw new UserAlreadyActivatedException($this->id);
}
$this->status = UserStatus::Active;
}
public function deactivate(): void
{
$this->status = UserStatus::Deactivated;
}
public function changeEmail(Email $newEmail): void
{
$this->ensureActive();
$this->email = $newEmail;
}
public function changePassword(HashedPassword $newPassword): void
{
$this->ensureActive();
$this->password = $newPassword;
}
public function changeName(string $newName): void
{
$this->ensureActive();
if (empty(trim($newName))) {
throw new InvalidUserNameException();
}
$this->name = $newName;
}
public function recordLogin(): void
{
$this->ensureActive();
$this->lastLoginAt = new DateTimeImmutable();
}
public function verifyPassword(string $plainPassword, PasswordHasherInterface $hasher): bool
{
return $hasher->verify($this->password, $plainPassword);
}
public function isActive(): bool
{
return $this->status === UserStatus::Active;
}
private function ensureActive(): void
{
if ($this->status === UserStatus::Deactivated) {
throw new UserDeactivatedException($this->id);
}
}
}
```
## Entity Design Principles
### 1. Behavior Over Data
```php
// BAD: Anemic entity
class Order
{
public function setStatus(string $status): void
{
$this->status = $status;
}
}
// GOOD: Rich entity with behavior
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
```php
// Protect invariants in every method
public function addLine(Product $product, int $quantity): void
{
// Invariant: Can only modify draft orders
if ($this->status !== OrderStatus::Draft) {
throw new CannotModifyConfirmedOrderException();
}
// Invariant: Quantity must be positive
if ($quantity <= 0) {
throw new InvalidQuantityException();
}
$this->lines[] = new OrderLine($product, $quantity);
}
```
### 3. State Transitions
```php
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,
self::Cancelled => false,
};
}
public function canBeCancelled(): bool
{
return $this->canTransitionTo(self::Cancelled);
}
}
```
## Generation Instructions
When asked to create an Entity:
1. **Identify the identity** (what makes it unique)
2. **Define the lifecycle** (statuses/states)
3. **List invariants** (business rules to protect)
4. **Design behavior methods** (what it can do)
5. **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 protectRelated Skills
acc-create-test-builder
Generates Test Data Builder and Object Mother patterns for PHP 8.5. Creates fluent builders with sensible defaults and factory methods for test data creation.
acc-create-query
Generates CQRS Queries and Handlers for PHP 8.5. Creates read-only query DTOs with handlers that return data without side effects. Includes unit tests.
acc-create-psr18-http-client
Generates PSR-18 HTTP Client implementation for PHP 8.5. Creates ClientInterface with request sending and exception handling. Includes unit tests.
acc-create-psr14-event-dispatcher
Generates PSR-14 Event Dispatcher implementation for PHP 8.5. Creates EventDispatcherInterface, ListenerProviderInterface, and StoppableEventInterface with event propagation. Includes unit tests.
acc-create-builder
Generates Builder pattern for PHP 8.5. Creates step-by-step object construction with fluent interface and validation. Includes unit tests.
acc-create-value-object
Generates DDD Value Objects for PHP 8.5. Creates immutable, self-validating objects with equality comparison. Includes unit tests.
acc-create-unit-test
Generates PHPUnit unit tests for PHP 8.5. Creates isolated tests with AAA pattern, proper naming, attributes, and one behavior per test. Supports Value Objects, Entities, Services.
acc-create-test-double
Generates test doubles (Mocks, Stubs, Fakes, Spies) for PHP 8.5. Creates appropriate double type based on testing needs with PHPUnit MockBuilder patterns.
acc-create-psr7-http-message
Generates PSR-7 HTTP Message implementations for PHP 8.5. Creates Request, Response, Stream, Uri, and ServerRequest classes with immutability. Includes unit tests.
acc-create-policy
Generates Policy pattern for PHP 8.5. Creates encapsulated business rules for authorization, validation, and domain constraints. Includes unit tests.
acc-create-null-object
Generates Null Object pattern for PHP 8.5. Creates safe default implementations eliminating null checks. Includes unit tests.
acc-create-command
Generates CQRS Commands and Handlers for PHP 8.5. Creates immutable command DTOs with handlers that modify state. Includes unit tests.