AgentSkillsCN

acc-create-command

为 PHP 8.5 生成 CQRS 命令与处理器模式。创建不可变的命令 DTO,并搭配可修改状态的处理器,同时附带单元测试。

SKILL.md
--- frontmatter
name: acc-create-command
description: Generates CQRS Commands and Handlers for PHP 8.5. Creates immutable command DTOs with handlers that modify state. Includes unit tests.

Command Generator

Generate CQRS-compliant Commands and Command Handlers with tests.

Command Characteristics

  • Immutable: final readonly class
  • Imperative naming: Verb + noun (CreateOrder, ConfirmPayment)
  • Self-validating: Validates invariants in constructor
  • Intent-revealing: Name describes what should happen
  • Returns void or ID: Never returns data

Generation Process

Step 1: Generate Command

Path: src/Application/{BoundedContext}/Command/

  1. {Name}Command.php — Immutable command DTO

Step 2: Generate Handler

Path: src/Application/{BoundedContext}/Handler/

  1. {Name}Handler.php — Command processor

Step 3: Generate Tests

Path: tests/Unit/Application/{BoundedContext}/


File Placement

ComponentPath
Commandsrc/Application/{BoundedContext}/Command/
Handlersrc/Application/{BoundedContext}/Handler/
Unit Teststests/Unit/Application/{BoundedContext}/

Command Naming Conventions

ActionCommand NameReturns
Create newCreateOrderCommandID
Confirm/ApproveConfirmOrderCommandvoid
Cancel/RejectCancelOrderCommandvoid
Update propertyUpdateShippingAddressCommandvoid
Add childAddOrderLineCommandvoid
Remove childRemoveOrderLineCommandvoid

Quick Template Reference

Command

php
final readonly class {Name}Command
{
    public function __construct(
        public {ValueObject} $id,
        public string $data
    ) {
        if (empty($data)) {
            throw new \InvalidArgumentException('Data is required');
        }
    }

    public static function fromArray(array $data): self
    {
        return new self(
            id: new {ValueObject}($data['id']),
            data: $data['data']
        );
    }
}

Handler (Update Flow)

php
final readonly class {Name}Handler
{
    public function __construct(
        private {Repository}Interface $repository,
        private EventDispatcherInterface $events
    ) {}

    public function __invoke({Name}Command $command): void
    {
        $aggregate = $this->repository->findById($command->id);

        if ($aggregate === null) {
            throw new NotFoundException($command->id);
        }

        $aggregate->doSomething($command->data);

        $this->repository->save($aggregate);

        foreach ($aggregate->releaseEvents() as $event) {
            $this->events->dispatch($event);
        }
    }
}

Handler (Create Flow)

php
public function __invoke(CreateCommand $command): AggregateId
{
    $aggregate = Aggregate::create(
        id: $this->repository->nextIdentity(),
        ...
    );

    $this->repository->save($aggregate);

    foreach ($aggregate->releaseEvents() as $event) {
        $this->events->dispatch($event);
    }

    return $aggregate->id();
}

Anti-patterns to Avoid

Anti-patternProblemSolution
Returning DataQuery through commandUse Query for reads
No ValidationInvalid commandsValidate in constructor
Business LogicHandler has decisionsDelegate to aggregate
Missing EventsEvents not dispatchedAlways dispatch after save
Direct PersistenceBypassing aggregateAlways use aggregate methods

References

For complete PHP templates and examples, see:

  • references/templates.md — Command, Handler, Test templates with patterns
  • references/examples.md — CreateOrder, ConfirmOrder, CancelOrder examples and tests