| name | acc-check-immutability |
| description | Analyzes PHP code for immutability violations. Checks Value Objects, Events, DTOs for readonly properties, no setters, final classes, and wither patterns. Ensures domain objects maintain invariants. |
Immutability Analyzer
Overview
This skill analyzes PHP DDD projects for immutability violations in Value Objects, Domain Events, DTOs, and Read Models. Immutability is crucial for maintaining invariants, thread safety, and predictable behavior.
Immutability Requirements by Type
| Type | Must Be Immutable | Key Checks |
|---|
| Value Object | โ
Required | readonly, no setters, final |
| Domain Event | โ
Required | readonly, no modification after creation |
| DTO | โ
Recommended | readonly, no business logic |
| Read Model | โ
Required | readonly, projection-only changes |
| Entity | โ ๏ธ Controlled | Setters via behavior methods only |
| Aggregate | โ ๏ธ Controlled | State changes via domain methods |
Detection Patterns
Phase 1: Identify Immutable Candidates
Glob: **/ValueObject/**/*.php
Glob: **/Domain/**/*Value.php
Glob: **/Domain/**/*VO.php
Grep: "final.*class.*implements.*ValueObject" --glob "**/*.php"
Glob: **/Event/**/*Event.php
Glob: **/Domain/**/*Event.php
Grep: "class.*Event\s*\{|final readonly class.*Event" --glob "**/*.php"
Glob: **/DTO/**/*.php
Glob: **/Application/**/*DTO.php
Glob: **/Application/**/*Request.php
Glob: **/Application/**/*Response.php
Glob: **/ReadModel/**/*.php
Glob: **/Projection/**/*.php
Grep: "class.*ReadModel|class.*View|class.*Projection" --glob "**/*.php"
Phase 2: Readonly Class Check
Grep: "readonly class|final readonly class" --glob "**/*.php"
Grep: "final class.*ValueObject|final class.*Event|final class.*DTO" --glob "**/*.php"
PHP 8.2+ Recommended Pattern:
final readonly class Email
{
public function __construct(
public string $value,
) {}
}
final class Email
{
private string $value;
public function __construct(string $value)
{
$this->value = $value;
}
}
Phase 3: Readonly Properties Check
Grep: "private string|private int|private float|private bool|private array" --glob "**/ValueObject/**/*.php"
Grep: "private string|private int|private float" --glob "**/Event/**/*.php"
Grep: "public string|public int|public float|public bool" --glob "**/Domain/**/*.php"
Phase 4: Setter Detection
Grep: "public function set[A-Z]" --glob "**/ValueObject/**/*.php"
Grep: "public function set[A-Z]" --glob "**/Event/**/*.php"
Grep: "public function set[A-Z]" --glob "**/DTO/**/*.php"
Grep: "\$this->[a-z]+ =" --glob "**/ValueObject/**/*.php"
Grep: "implements.*ArrayAccess" --glob "**/ValueObject/**/*.php"
Phase 5: Final Class Check
Grep: "^class [A-Z].*ValueObject|^abstract class.*ValueObject" --glob "**/ValueObject/**/*.php"
Grep: "^class [A-Z].*Event\s*\{" --glob "**/Event/**/*.php"
Grep: "^class [A-Z].*DTO|^class [A-Z].*Request|^class [A-Z].*Response" --glob "**/DTO/**/*.php"
Phase 6: Wither Pattern Check
Grep: "return new self\(|return new static\(" --glob "**/ValueObject/**/*.php"
Grep: "public function with[A-Z]" --glob "**/ValueObject/**/*.php" -A 5
Grep: "public function update|public function change|public function modify" --glob "**/ValueObject/**/*.php"
Wither Pattern Example:
final readonly class Money
{
public function __construct(
public int $amount,
public Currency $currency,
) {}
public function withAmount(int $amount): self
{
return new self($amount, $this->currency);
}
}
final class Money
{
public function setAmount(int $amount): void
{
$this->amount = $amount;
}
}
Phase 7: Collection Immutability
Grep: "private array" --glob "**/ValueObject/**/*.php"
Grep: "array_push|unset\(|\\$this->items\[\]" --glob "**/ValueObject/**/*.php"
Grep: "return \$this->[a-z]+;" --glob "**/ValueObject/**/*.php"
Phase 8: DateTimeImmutable Check
Grep: "DateTime[^I]|\\\\DateTime " --glob "**/Domain/**/*.php"
Grep: "new DateTime\(" --glob "**/Domain/**/*.php"
Grep: "DateTimeImmutable" --glob "**/Domain/**/*.php"
Report Format
# Immutability Analysis Report
## Summary
| Type | Total | Fully Immutable | Issues |
|------|-------|-----------------|--------|
| Value Objects | 15 | 12 | 3 |
| Domain Events | 8 | 6 | 2 |
| DTOs | 10 | 8 | 2 |
| Read Models | 4 | 4 | 0 |
**Overall Immutability Score: 86%**
## Critical Issues
### IMM-001: Mutable Value Object
- **File:** `src/Domain/Order/ValueObject/Money.php`
- **Issue:** Public setter method found
- **Code:**
```php
public function setAmount(int $amount): void
{
$this->amount = $amount;
}
IMM-002: Non-readonly Event
- File:
src/Domain/Order/Event/OrderCreatedEvent.php
- Issue: Class not marked as
readonly, properties mutable
- Code:
final class OrderCreatedEvent
{
private string $orderId;
- Expected:
final readonly class OrderCreatedEvent
{
public function __construct(
public string $orderId,
- Skills:
acc-create-domain-event
IMM-003: DateTime Instead of DateTimeImmutable
- File:
src/Domain/User/Entity/User.php:45
- Issue: Using mutable DateTime
- Code:
private DateTime $createdAt
- Expected:
private DateTimeImmutable $createdAt
- Impact: Date can be accidentally modified
Warning Issues
IMM-004: Non-final Value Object
- File:
src/Domain/Shared/ValueObject/Address.php
- Issue: Class not marked as
final
- Impact: Subclasses could break immutability contract
IMM-005: Array Mutation
- File:
src/Domain/Order/ValueObject/OrderItems.php:34
- Issue: Array property modified after construction
- Code:
$this->items[] = $item;
- Refactoring: Return new collection instance
IMM-006: Missing Readonly Properties
- File:
src/Application/DTO/CreateOrderDTO.php
- Issue: Properties not readonly
- Code:
public string $customerId;
public array $items;
- Expected:
public readonly string $customerId,
public readonly array $items,
Compliance by Layer
| Layer | Compliance | Notes |
|---|
| Domain/ValueObject | 80% | 3 VOs need refactoring |
| Domain/Event | 75% | 2 events need readonly |
| Application/DTO | 80% | 2 DTOs need readonly |
| Infrastructure/ReadModel | 100% | All compliant |
Refactoring Recommendations
Immediate Actions
- Add
readonly keyword to all Value Objects
- Replace
DateTime with DateTimeImmutable
- Remove setters from Events and DTOs
Wither Method Additions
- Add
withAmount() to Money
- Add
withItems() to OrderItems
Class Modifiers
- Add
final to all Value Objects
- Consider
readonly class for PHP 8.2+
## Immutability Patterns
### Fully Immutable Class (PHP 8.2+)
```php
final readonly class Email
{
public function __construct(
public string $value,
) {
$this->validate($value);
}
private function validate(string $value): void
{
if (!filter_var($value, FILTER_VALIDATE_EMAIL)) {
throw new InvalidArgumentException('Invalid email');
}
}
public function equals(self $other): bool
{
return $this->value === $other->value;
}
}
Wither Pattern for Modifications
final readonly class Money
{
public function __construct(
public int $amount,
public Currency $currency,
) {}
public function add(self $other): self
{
if (!$this->currency->equals($other->currency)) {
throw new CurrencyMismatchException();
}
return new self($this->amount + $other->amount, $this->currency);
}
public function withAmount(int $amount): self
{
return new self($amount, $this->currency);
}
}
Immutable Collection
final readonly class OrderItems
{
public function __construct(
private array $items,
) {}
public function add(OrderItem $item): self
{
return new self([...$this->items, $item]);
}
public function remove(OrderItem $item): self
{
return new self(
array_filter($this->items, fn($i) => !$i->equals($item))
);
}
public function toArray(): array
{
return $this->items;
}
}
Quick Analysis Commands
echo "=== Non-readonly Value Objects ===" && \
grep -rn "final class" --include="*.php" src/Domain/*/ValueObject/ | grep -v "readonly" && \
echo "=== Setters in Immutable Types ===" && \
grep -rn "public function set[A-Z]" --include="*.php" src/Domain/*/ValueObject/ src/Domain/*/Event/ && \
echo "=== Mutable DateTime ===" && \
grep -rn "DateTime[^I]" --include="*.php" src/Domain/ | grep -v "DateTimeImmutable" && \
echo "=== Array Mutations ===" && \
grep -rn "\$this->[a-z]*\[\]" --include="*.php" src/Domain/*/ValueObject/
Integration
Works with:
acc-create-value-object โ Generate immutable VOs
acc-create-domain-event โ Generate immutable events
acc-create-dto โ Generate immutable DTOs
acc-structural-auditor โ Architectural compliance
acc-behavioral-auditor โ Event Sourcing compliance
References
- PHP 8.2 readonly classes RFC
- "Domain-Driven Design" (Eric Evans) โ Value Objects chapter
- "Implementing DDD" (Vaughn Vernon) โ Immutability patterns