Skip to content
cccskills
FreePublished on GitHub

acc-create-value-object

Generates DDD Value Objects for PHP 8.5. Creates immutable, self-validating objects with equality comparison. Includes unit tests.

The English summary is being prepared. Showing the original description.

See how to install

Files included (1)

  • SKILL.md8.3 KB

SKILL.md (original)

Review the instructions the agent will receive before you install.

Value Object Generator

Generate DDD-compliant Value Objects with validation, equality, and tests.

Value Object Characteristics

  • Immutable: final readonly class
  • Self-validating: Validates in constructor
  • Equality by value: equals() method compares values
  • No identity: No ID, compared by content
  • Encapsulates concept: Represents domain concept

Template

<?php

declare(strict_types=1);

namespace Domain\{BoundedContext}\ValueObject;

use Domain\{BoundedContext}\Exception\Invalid{Name}Exception;

final readonly class {Name}
{
    public function __construct(
        public {type} $value
    ) {
        {validation}
    }

    public function equals(self $other): bool
    {
        return $this->value === $other->value;
    }

    public function __toString(): string
    {
        return (string) $this->value;
    }
}

Test Template

<?php

declare(strict_types=1);

namespace Tests\Unit\Domain\{BoundedContext}\ValueObject;

use Domain\{BoundedContext}\ValueObject\{Name};
use Domain\{BoundedContext}\Exception\Invalid{Name}Exception;
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 testCreatesWithValidValue(): void
    {
        $vo = new {Name}({validValue});

        self::assertSame({validValue}, $vo->value);
    }

    public function testThrowsExceptionForInvalidValue(): void
    {
        $this->expectException(Invalid{Name}Exception::class);

        new {Name}({invalidValue});
    }

    public function testEquality(): void
    {
        $vo1 = new {Name}({validValue});
        $vo2 = new {Name}({validValue});
        $vo3 = new {Name}({differentValue});

        self::assertTrue($vo1->equals($vo2));
        self::assertFalse($vo1->equals($vo3));
    }

    public function testToString(): void
    {
        $vo = new {Name}({validValue});

        self::assertSame({expectedString}, (string) $vo);
    }
}

Common Value Objects

Email

final readonly class Email
{
    public function __construct(
        public string $value
    ) {
        if (!filter_var($value, FILTER_VALIDATE_EMAIL)) {
            throw new InvalidEmailException($value);
        }
    }

    public function equals(self $other): bool
    {
        return strtolower($this->value) === strtolower($other->value);
    }

    public function domain(): string
    {
        return substr($this->value, strpos($this->value, '@') + 1);
    }

    public function __toString(): string
    {
        return $this->value;
    }
}

Money

final readonly class Money
{
    public function __construct(
        public int $cents,
        public string $currency
    ) {
        if ($cents < 0) {
            throw new InvalidMoneyException('Amount cannot be negative');
        }
        if (strlen($currency) !== 3) {
            throw new InvalidMoneyException('Currency must be 3 characters');
        }
    }

    public static function zero(string $currency): self
    {
        return new self(0, $currency);
    }

    public static function fromFloat(float $amount, string $currency): self
    {
        return new self((int) round($amount * 100), $currency);
    }

    public function add(self $other): self
    {
        $this->assertSameCurrency($other);
        return new self($this->cents + $other->cents, $this->currency);
    }

    public function subtract(self $other): self
    {
        $this->assertSameCurrency($other);
        return new self($this->cents - $other->cents, $this->currency);
    }

    public function multiply(int $factor): self
    {
        return new self($this->cents * $factor, $this->currency);
    }

    public function isGreaterThan(self $other): bool
    {
        $this->assertSameCurrency($other);
        return $this->cents > $other->cents;
    }

    public function isZero(): bool
    {
        return $this->cents === 0;
    }

    public function isPositive(): bool
    {
        return $this->cents > 0;
    }

    public function equals(self $other): bool
    {
        return $this->cents === $other->cents && $this->currency === $other->currency;
    }

    public function format(): string
    {
        return number_format($this->cents / 100, 2) . ' ' . $this->currency;
    }

    private function assertSameCurrency(self $other): void
    {
        if ($this->currency !== $other->currency) {
            throw new CurrencyMismatchException($this->currency, $other->currency);
        }
    }
}

UUID-based ID

final readonly class OrderId
{
    public function __construct(
        public string $value
    ) {
        if (!preg_match('/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i', $value)) {
            throw new InvalidOrderIdException($value);
        }
    }

    public static function generate(): self
    {
        return new self(self::uuid4());
    }

    public function equals(self $other): bool
    {
        return $this->value === $other->value;
    }

    public function __toString(): string
    {
        return $this->value;
    }

    private static function uuid4(): string
    {
        $data = random_bytes(16);
        $data[6] = chr(ord($data[6]) & 0x0f | 0x40);
        $data[8] = chr(ord($data[8]) & 0x3f | 0x80);

        return vsprintf('%s%s-%s-%s-%s-%s%s%s', str_split(bin2hex($data), 4));
    }
}

Address (Composite)

final readonly class Address
{
    public function __construct(
        public string $street,
        public string $city,
        public string $postalCode,
        public string $country
    ) {
        if (empty(trim($street))) {
            throw new InvalidAddressException('Street cannot be empty');
        }
        if (empty(trim($city))) {
            throw new InvalidAddressException('City cannot be empty');
        }
        if (strlen($country) !== 2) {
            throw new InvalidAddressException('Country must be ISO 3166-1 alpha-2');
        }
    }

    public function equals(self $other): bool
    {
        return $this->street === $other->street
            && $this->city === $other->city
            && $this->postalCode === $other->postalCode
            && $this->country === $other->country;
    }

    public function format(): string
    {
        return "{$this->street}\n{$this->postalCode} {$this->city}\n{$this->country}";
    }
}

DateRange

final readonly class DateRange
{
    public function __construct(
        public DateTimeImmutable $start,
        public DateTimeImmutable $end
    ) {
        if ($end < $start) {
            throw new InvalidDateRangeException('End date must be after start date');
        }
    }

    public function contains(DateTimeImmutable $date): bool
    {
        return $date >= $this->start && $date <= $this->end;
    }

    public function overlaps(self $other): bool
    {
        return $this->start <= $other->end && $this->end >= $other->start;
    }

    public function days(): int
    {
        return (int) $this->start->diff($this->end)->days;
    }

    public function equals(self $other): bool
    {
        return $this->start == $other->start && $this->end == $other->end;
    }
}

Generation Instructions

When asked to create a Value Object:

  1. Identify the concept being modeled
  2. Determine validation rules from domain requirements
  3. Choose appropriate type (string, int, composite)
  4. Add domain-specific methods if needed
  5. Generate corresponding test with valid/invalid cases

Naming Conventions

ConceptClass NameException
Email addressEmailInvalidEmailException
Money amountMoneyInvalidMoneyException
Order identifierOrderIdInvalidOrderIdException
Physical addressAddressInvalidAddressException
Phone numberPhoneInvalidPhoneException
Date rangeDateRangeInvalidDateRangeException

Usage

To generate a Value Object, provide:

  • Name (e.g., "Email", "CustomerId")
  • Bounded Context (e.g., "Order", "Customer")
  • Validation rules
  • Any special methods needed

Reviews

No reviews yet. Tell others what you thought.

Comments

No login needed. Share how it worked for you, ask a question, or note where it did not run.

No comments yet. Be the first to write one.

Comments are published right away. Do not include personal information such as email addresses or phone numbers. Up to 2 URLs.

0 / 1,000 characters

This form is protected by reCAPTCHA, and the Google Privacy Policy and Terms of Service apply.

More skills from this repository

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.

The English summary is being prepared. Showing the original description.

MikeCheng1208/BattleTree2Updated Jul 22, 2026

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

The English summary is being prepared. Showing the original description.

MikeCheng1208/BattleTree2Updated Jul 22, 2026

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

The English summary is being prepared. Showing the original description.

MikeCheng1208/BattleTree2Updated Jul 22, 2026

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

The English summary is being prepared. Showing the original description.

MikeCheng1208/BattleTree2Updated Jul 22, 2026

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.

The English summary is being prepared. Showing the original description.

MikeCheng1208/BattleTree2Updated Jul 22, 2026

[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.

The English summary is being prepared. Showing the original description.

MikeCheng1208/BattleTree2Updated Jul 22, 2026

See all skills by MikeCheng1208

Report a problem with this skill