Design Value Objects for domain concepts and DTOs for data transfer with proper immutability and validation
/plugin marketplace add MakFly/superpowers-symfony/plugin install makfly-superpowers-symfony@MakFly/superpowers-symfonyThis skill inherits all available tools. When active, it can use any tool Claude has access to.
Value Objects are immutable objects defined by their attributes, not identity.
<?php
// src/Domain/ValueObject/Money.php
namespace App\Domain\ValueObject;
final readonly class Money
{
private function __construct(
private int $amount, // In cents
private string $currency,
) {
if ($amount < 0) {
throw new \InvalidArgumentException('Amount cannot be negative');
}
if (strlen($currency) !== 3) {
throw new \InvalidArgumentException('Currency must be ISO 4217 code');
}
}
public static function of(int $amount, string $currency): self
{
return new self($amount, strtoupper($currency));
}
public static function EUR(int $amount): self
{
return new self($amount, 'EUR');
}
public static function zero(string $currency = 'EUR'): self
{
return new self(0, strtoupper($currency));
}
public function add(self $other): self
{
$this->assertSameCurrency($other);
return new self($this->amount + $other->amount, $this->currency);
}
public function subtract(self $other): self
{
$this->assertSameCurrency($other);
return new self($this->amount - $other->amount, $this->currency);
}
public function multiply(float $multiplier): self
{
return new self((int) round($this->amount * $multiplier), $this->currency);
}
public function getAmount(): int
{
return $this->amount;
}
public function getCurrency(): string
{
return $this->currency;
}
public function format(): string
{
return number_format($this->amount / 100, 2) . ' ' . $this->currency;
}
public function equals(self $other): bool
{
return $this->amount === $other->amount
&& $this->currency === $other->currency;
}
public function isGreaterThan(self $other): bool
{
$this->assertSameCurrency($other);
return $this->amount > $other->amount;
}
public function isZero(): bool
{
return $this->amount === 0;
}
private function assertSameCurrency(self $other): void
{
if ($this->currency !== $other->currency) {
throw new \InvalidArgumentException(
"Cannot operate on different currencies: {$this->currency} vs {$other->currency}"
);
}
}
}
<?php
// src/Domain/ValueObject/Email.php
namespace App\Domain\ValueObject;
final readonly class Email
{
private function __construct(
private string $value,
) {
if (!filter_var($value, FILTER_VALIDATE_EMAIL)) {
throw new \InvalidArgumentException("Invalid email: {$value}");
}
}
public static function fromString(string $email): self
{
return new self(strtolower(trim($email)));
}
public function getValue(): string
{
return $this->value;
}
public function getDomain(): string
{
return substr($this->value, strpos($this->value, '@') + 1);
}
public function equals(self $other): bool
{
return $this->value === $other->value;
}
public function __toString(): string
{
return $this->value;
}
}
<?php
// src/Domain/ValueObject/Address.php
namespace App\Domain\ValueObject;
final readonly class Address
{
public function __construct(
public string $street,
public string $city,
public string $postalCode,
public string $country,
public ?string $state = null,
) {
if (empty($street) || empty($city) || empty($postalCode) || empty($country)) {
throw new \InvalidArgumentException('Address fields cannot be empty');
}
}
public function withStreet(string $street): self
{
return new self($street, $this->city, $this->postalCode, $this->country, $this->state);
}
public function format(): string
{
$parts = [$this->street, $this->postalCode . ' ' . $this->city];
if ($this->state) {
$parts[] = $this->state;
}
$parts[] = $this->country;
return implode("\n", $parts);
}
public function equals(self $other): bool
{
return $this->street === $other->street
&& $this->city === $other->city
&& $this->postalCode === $other->postalCode
&& $this->country === $other->country
&& $this->state === $other->state;
}
}
Store Value Objects in database:
<?php
// src/Domain/ValueObject/Money.php
use Doctrine\ORM\Mapping as ORM;
#[ORM\Embeddable]
final readonly class Money
{
#[ORM\Column(type: 'integer')]
private int $amount;
#[ORM\Column(type: 'string', length: 3)]
private string $currency;
// ... rest of class
}
// src/Entity/Order.php
#[ORM\Entity]
class Order
{
#[ORM\Embedded(class: Money::class)]
private Money $total;
public function getTotal(): Money
{
return $this->total;
}
}
DTOs carry data between layers without behavior.
<?php
// src/Dto/CreateOrderInput.php
namespace App\Dto;
use Symfony\Component\Validator\Constraints as Assert;
final readonly class CreateOrderInput
{
public function __construct(
#[Assert\NotBlank]
#[Assert\Positive]
public int $customerId,
#[Assert\NotBlank]
#[Assert\Count(min: 1, minMessage: 'Order must have at least one item')]
#[Assert\Valid]
public array $items,
#[Assert\Length(max: 20)]
public ?string $couponCode = null,
) {}
}
// src/Dto/OrderItemInput.php
final readonly class OrderItemInput
{
public function __construct(
#[Assert\NotBlank]
#[Assert\Positive]
public int $productId,
#[Assert\NotBlank]
#[Assert\Positive]
#[Assert\LessThanOrEqual(100)]
public int $quantity,
) {}
}
<?php
// src/Dto/OrderOutput.php
namespace App\Dto;
use App\Entity\Order;
final readonly class OrderOutput
{
public function __construct(
public string $id,
public int $customerId,
public array $items,
public MoneyOutput $total,
public string $status,
public string $createdAt,
) {}
public static function fromEntity(Order $order): self
{
return new self(
id: $order->getId(),
customerId: $order->getCustomer()->getId(),
items: array_map(
fn($item) => OrderItemOutput::fromEntity($item),
$order->getItems()->toArray()
),
total: MoneyOutput::fromValueObject($order->getTotal()),
status: $order->getStatus()->value,
createdAt: $order->getCreatedAt()->format('c'),
);
}
}
// src/Dto/MoneyOutput.php
final readonly class MoneyOutput
{
public function __construct(
public int $amount,
public string $currency,
public string $formatted,
) {}
public static function fromValueObject(Money $money): self
{
return new self(
amount: $money->getAmount(),
currency: $money->getCurrency(),
formatted: $money->format(),
);
}
}
<?php
// src/Entity/Order.php
use ApiPlatform\Metadata\ApiResource;
use ApiPlatform\Metadata\Post;
use App\Dto\CreateOrderInput;
use App\Dto\OrderOutput;
#[ApiResource(
operations: [
new Post(
input: CreateOrderInput::class,
output: OrderOutput::class,
processor: CreateOrderProcessor::class,
),
],
)]
class Order { /* ... */ }
# config/packages/serializer.yaml
framework:
serializer:
mapping:
paths:
- '%kernel.project_dir%/config/serializer'
# config/serializer/Money.yaml
App\Domain\ValueObject\Money:
attributes:
amount:
groups: ['read']
currency:
groups: ['read']
Creating algorithmic art using p5.js with seeded randomness and interactive parameter exploration. Use this when users request creating art using code, generative art, algorithmic art, flow fields, or particle systems. Create original algorithmic art rather than copying existing artists' work to avoid copyright violations.
Applies Anthropic's official brand colors and typography to any sort of artifact that may benefit from having Anthropic's look-and-feel. Use it when brand colors or style guidelines, visual formatting, or company design standards apply.
Create beautiful visual art in .png and .pdf documents using design philosophy. You should use this skill when the user asks to create a poster, piece of art, design, or other static piece. Create original visual designs, never copying existing artists' work to avoid copyright violations.