본문으로 건너뛰기
cccskills
무료GitHub에 공개

acc-create-entity

Generates DDD Entities for PHP 8.5. Creates identity-based objects with behavior, state transitions, and invariant protection. Includes unit tests.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

설치 방법 보기

포함된 파일(1)

  • SKILL.md11.0 KB

SKILL.md(원문)

설치하기 전에 에이전트에게 전달되는 지시 내용을 확인할 수 있습니다.

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
    {
        $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

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

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

// 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

// 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

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

ConceptMethod PatternException
State changeconfirm(), activate(), cancel()InvalidStateTransitionException
Add relationaddLine(), addItem()CannotModifyException
Update propertychangeEmail(), updateName()InvalidValueException
Query stateisActive(), 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

리뷰

아직 리뷰가 없습니다. 사용해 본 소감을 남겨 주세요.

댓글

로그인 없이 쓸 수 있습니다. 사용 후기, 질문, 작동하지 않은 환경 등을 편하게 남겨 주세요.

아직 댓글이 없습니다. 첫 댓글을 남겨 보세요.

보내면 바로 공개됩니다. 이메일 주소나 전화번호 같은 개인 정보는 쓰지 마세요. URL은 2개까지입니다.

0 / 1,000자

이 양식은 reCAPTCHA로 보호되며 Google 개인정보처리방침 및 서비스 약관이 적용됩니다.

같은 저장소의 스킬

Master backend architectures, REST API design, microservices, and server-side development across multiple languages. Use this as a central index to access specialized sub-skills.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

MikeCheng1208/BattleTree22026년 7월 22일 업데이트

在每次回复中使用固定前缀并列出本次实际参考的指令/文件,便于校验是否遵循上下文与规则。

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

MikeCheng1208/BattleTree22026년 7월 22일 업데이트

Automate 21risk tasks via Rube MCP (Composio). Always search tools first for current schemas.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

MikeCheng1208/BattleTree22026년 7월 22일 업데이트

Automate 2chat tasks via Rube MCP (Composio). Always search tools first for current schemas.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

MikeCheng1208/BattleTree22026년 7월 22일 업데이트

Arquitecto de Soluciones Principal y Consultor Tecnológico de Andru.ia. Diagnostica y traza la hoja de ruta óptima para proyectos de IA en español.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

MikeCheng1208/BattleTree22026년 7월 22일 업데이트

[00] META. Orchestrate skills into dynamic chains for complex tasks. Analyzes the task, discovers available skills, builds an optimal chain, explains why each skill is needed, and executes step-by-step with user confirmation. Use for any complex task requiring multiple thinking/research/analysis steps. Triggers on "plan this", "how to approach", "what's the strategy", "build a plan", or any multi-step problem requiring skill orchestration.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

MikeCheng1208/BattleTree22026년 7월 22일 업데이트

MikeCheng1208의 스킬 모두 보기

이 스킬의 문제 신고