acc-create-value-object

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

16 stars

Best use case

acc-create-value-object is best used when you need a repeatable AI agent workflow instead of a one-off prompt.

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

Teams using acc-create-value-object 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

$curl -o ~/.claude/skills/acc-create-value-object/SKILL.md --create-dirs "https://raw.githubusercontent.com/diegosouzapw/awesome-omni-skill/main/skills/testing-security/acc-create-value-object/SKILL.md"

Manual Installation

  1. Download SKILL.md from GitHub
  2. Place it in .claude/skills/acc-create-value-object/SKILL.md inside your project
  3. Restart your AI agent — it will auto-discover the skill

How acc-create-value-object Compares

Feature / Agentacc-create-value-objectStandard Approach
Platform SupportNot specifiedLimited / Varies
Context Awareness High Baseline
Installation ComplexityUnknownN/A

Frequently Asked Questions

What does this skill do?

Generates DDD Value Objects for PHP 8.5. Creates immutable, self-validating objects with equality comparison. 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

# 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
<?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
<?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

```php
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

```php
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

```php
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)

```php
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

```php
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

| Concept | Class Name | Exception |
|---------|------------|-----------|
| Email address | `Email` | `InvalidEmailException` |
| Money amount | `Money` | `InvalidMoneyException` |
| Order identifier | `OrderId` | `InvalidOrderIdException` |
| Physical address | `Address` | `InvalidAddressException` |
| Phone number | `Phone` | `InvalidPhoneException` |
| Date range | `DateRange` | `InvalidDateRangeException` |

## Usage

To generate a Value Object, provide:
- Name (e.g., "Email", "CustomerId")
- Bounded Context (e.g., "Order", "Customer")
- Validation rules
- Any special methods needed

Related Skills

acc-create-unit-test

16
from diegosouzapw/awesome-omni-skill

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

16
from diegosouzapw/awesome-omni-skill

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

16
from diegosouzapw/awesome-omni-skill

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

16
from diegosouzapw/awesome-omni-skill

Generates Policy pattern for PHP 8.5. Creates encapsulated business rules for authorization, validation, and domain constraints. Includes unit tests.

acc-create-null-object

16
from diegosouzapw/awesome-omni-skill

Generates Null Object pattern for PHP 8.5. Creates safe default implementations eliminating null checks. Includes unit tests.

acc-create-command

16
from diegosouzapw/awesome-omni-skill

Generates CQRS Commands and Handlers for PHP 8.5. Creates immutable command DTOs with handlers that modify state. Includes unit tests.

doc-sys: Create System Requirements (Layer 6)

16
from diegosouzapw/awesome-omni-skill

Create System Requirements (SYS) - Layer 6 artifact defining functional requirements and quality attributes

create-prd

16
from diegosouzapw/awesome-omni-skill

This skill should be used when the user asks to "创建PRD", "写产品需求文档", "生成PRD", "新建PRD", "create PRD", "write product requirements document", or mentions "产品需求文档", "PRD模板". Automatically generates comprehensive Chinese PRD documents following 2026 best practices.

Create Jira Feature

16
from diegosouzapw/awesome-omni-skill

Implementation guide for creating Jira features representing strategic objectives and market problems

create-feature

16
from diegosouzapw/awesome-omni-skill

Creates Features following the T-Minus-15 process template. Features represent significant deliverables that contain multiple User Stories. Includes proper metadata, MoSCoW prioritization, effort estimates, deliverables, and benefit hypothesis.

create-feature-branch

16
from diegosouzapw/awesome-omni-skill

Create properly named feature branch from development with remote tracking, following WescoBar naming conventions and git best practices

alfworld-object-state-modifier

16
from diegosouzapw/awesome-omni-skill

This skill uses an appliance to change the state of an object (e.g., cooling, heating, cleaning). It should be triggered when the task requires altering an object's temperature or cleanliness using a specific device (like cooling with a fridge or heating with a microwave). The skill requires the object, the target state, and the appliance as inputs, and executes the corresponding modifier action (e.g., 'cool X with Y').