<< All versions
Skill v1.0.1
currentAutomated scan100/100dykyi-roman/awesome-claude-code/check-immutability
2 files
──Details
PublishedAugust 26, 2026 at 06:01 AM
Content Hashsha256:8eb43ed0c212a525...
Git SHA7e1fea8899b7
Bump Typepatch
──Files
Files (1 file, 10.9 KB)
SKILL.md10.9 KBactive
SKILL.md · 421 lines · 10.9 KB
version: "1.0.1" name: 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
bash
# Value ObjectsGlob: **/ValueObject/**/*.phpGlob: **/Domain/**/*Value.phpGlob: **/Domain/**/*VO.phpGrep: "final.*class.*implements.*ValueObject" --glob "**/*.php"# Domain EventsGlob: **/Event/**/*Event.phpGlob: **/Domain/**/*Event.phpGrep: "class.*Event\s*\{|final readonly class.*Event" --glob "**/*.php"# DTOsGlob: **/DTO/**/*.phpGlob: **/Application/**/*DTO.phpGlob: **/Application/**/*Request.phpGlob: **/Application/**/*Response.php# Read ModelsGlob: **/ReadModel/**/*.phpGlob: **/Projection/**/*.phpGrep: "class.*ReadModel|class.*View|class.*Projection" --glob "**/*.php"
Phase 2: Readonly Class Check
bash
# PHP 8.2+ readonly classes (best practice)Grep: "readonly class|final readonly class" --glob "**/*.php"# Missing readonly keywordGrep: "final class.*ValueObject|final class.*Event|final class.*DTO" --glob "**/*.php"# Should be: final readonly class
PHP 8.2+ Recommended Pattern:
php
// Goodfinal readonly class Email{public function __construct(public string $value,) {}}// Bad (pre-8.2 style)final class Email{private string $value;public function __construct(string $value){$this->value = $value;}}
Phase 3: Readonly Properties Check
bash
# Properties without readonlyGrep: "private string|private int|private float|private bool|private array" --glob "**/ValueObject/**/*.php"Grep: "private string|private int|private float" --glob "**/Event/**/*.php"# Expected: private readonly string, or use readonly class# Public non-readonly properties (critical)Grep: "public string|public int|public float|public bool" --glob "**/Domain/**/*.php"# Should be: public readonly or private with getter
Phase 4: Setter Detection
bash
# Explicit setters in immutable typesGrep: "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"# Property assignment outside constructorGrep: "\$this->[a-z]+ =" --glob "**/ValueObject/**/*.php"# Check if inside __construct or not# ArrayAccess modificationsGrep: "implements.*ArrayAccess" --glob "**/ValueObject/**/*.php"# offsetSet should throw or return new instance
Phase 5: Final Class Check
bash
# Non-final Value ObjectsGrep: "^class [A-Z].*ValueObject|^abstract class.*ValueObject" --glob "**/ValueObject/**/*.php"# Should be final# Non-final EventsGrep: "^class [A-Z].*Event\s*\{" --glob "**/Event/**/*.php"# Should be final# Non-final DTOsGrep: "^class [A-Z].*DTO|^class [A-Z].*Request|^class [A-Z].*Response" --glob "**/DTO/**/*.php"
Phase 6: Wither Pattern Check
bash
# Methods returning new instances (wither pattern)Grep: "return new self\(|return new static\(" --glob "**/ValueObject/**/*.php"# Methods that should use wither but mutateGrep: "public function with[A-Z]" --glob "**/ValueObject/**/*.php" -A 5# Check if returns new instance or mutates# Missing wither methodsGrep: "public function update|public function change|public function modify" --glob "**/ValueObject/**/*.php"# These should be wither methods returning new instance
Wither Pattern Example:
php
// Good (wither pattern)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);}}// Bad (mutation)final class Money{public function setAmount(int $amount): void{$this->amount = $amount; // Mutation!}}
Phase 7: Collection Immutability
bash
# Mutable array propertiesGrep: "private array" --glob "**/ValueObject/**/*.php"# Check for array_push, unset, etc.# Collection modificationsGrep: "array_push|unset\(|\\$this->items\[\]" --glob "**/ValueObject/**/*.php"# Missing array return by valueGrep: "return \$this->[a-z]+;" --glob "**/ValueObject/**/*.php"# Arrays should be returned as copies or immutable iterators
Phase 8: DateTimeImmutable Check
bash
# Using DateTime instead of DateTimeImmutableGrep: "DateTime[^I]|\\\\DateTime " --glob "**/Domain/**/*.php"Grep: "new DateTime\(" --glob "**/Domain/**/*.php"# Expected: DateTimeImmutableGrep: "DateTimeImmutable" --glob "**/Domain/**/*.php"
Report Format
markdown
# 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:**```phppublic function setAmount(int $amount): void{$this->amount = $amount;}```-**Expected:** Use wither pattern```phppublic function withAmount(int $amount): self{return new self($amount, $this->currency);}```-**Skills:** `create-value-object`### IMM-002: Non-readonly Event-**File:** `src/Domain/Order/Event/OrderCreatedEvent.php`-**Issue:** Class not marked as `readonly`, properties mutable-**Code:**```phpfinal class OrderCreatedEvent{private string $orderId;```-**Expected:**```phpfinal readonly class OrderCreatedEvent{public function __construct(public string $orderId,```-**Skills:** `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:**```phppublic string $customerId;public array $items;```-**Expected:**```phppublic 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 Actions1.Add `readonly` keyword to all Value Objects2.Replace `DateTime` with `DateTimeImmutable`3.Remove setters from Events and DTOs### Wither Method Additions4.Add `withAmount()` to Money5.Add `withItems()` to OrderItems### Class Modifiers6.Add `final` to all Value Objects7.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
php
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
php
final readonly class OrderItems{/** @param array<OrderItem> $items */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)));}/** @return array<OrderItem> */public function toArray(): array{return $this->items;}}
Quick Analysis Commands
bash
# Check immutabilityecho "=== 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:
create-value-object— Generate immutable VOscreate-domain-event— Generate immutable eventscreate-dto— Generate immutable DTOsstructural-auditor— Architectural compliancebehavioral-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