← CodeClarityLab Home
Browse by Category
+ added · updated 7d
← Back to glossary

Fixing Primitive Obsession with Value Objects

quality Intermediate

Also Known As

value object refactoring tiny types micro types

TL;DR

Replacing raw strings, ints, and floats representing domain concepts with dedicated Value Object classes that enforce invariants at construction.

Explanation

Primitive obsession uses raw types where domain concepts belong: string $email, float $price, string $status. Problems: no validation enforced, no type safety distinguishing 'email string' from 'name string', business logic scattered across multiple callers. The fix: create small Value Object classes — Email, Money, OrderStatus — that validate and encapsulate the concept. Constructors throw on invalid input, so a valid VO instance is always well-formed. Methods can be added: $money->add($other), $email->domain(). PHP 8.1 readonly classes make VOs concise. PHPStan recognises distinct types, catching swapped-argument bugs statically. Start with the most-duplicated validation logic and extract outward.

Common Misconception

Value objects are overkill for simple domains. Even in simple domains, a Money class that prevents adding EUR to GBP, or an Email class that validates on construction, catches entire categories of bugs at the type level that string primitives cannot.

Why It Matters

Replacing primitive types with domain-specific value objects makes invalid states unrepresentable and moves validation to the type boundary — no more scattered if(empty($email)) checks across the codebase.

Common Mistakes

  • Value objects with setters — they should be immutable; create a new instance for changes.
  • Not overriding __toString() for value objects used in string contexts — causes type errors.
  • Value object equality checked with == instead of a dedicated equals() method or comparing properties.
  • Wrapping every primitive — only wrap primitives that carry domain rules or validation; plain int IDs often do not need wrapping.

Code Examples

✗ Vulnerable
function charge(string $email, float $amount, string $currency): void {
    // Which string is which? float can be negative?
}
✓ Fixed
function charge(Email $email, Money $amount): void {
    // Email and Money enforce their own invariants
}

Added 15 Mar 2026
Edited 22 Mar 2026
Views 29
Rate this term
No ratings yet
🤖 AI Guestbook educational data only
| |
Last 30 days
0 pings F 0 pings S 2 pings S 1 ping M 0 pings T 0 pings W 0 pings T 0 pings F 0 pings S 1 ping S 1 ping M 0 pings T 0 pings W 2 pings T 0 pings F 0 pings S 2 pings S 0 pings M 0 pings T 0 pings W 1 ping T 0 pings F 0 pings S 1 ping S 0 pings M 0 pings T 0 pings W 0 pings T 0 pings F 0 pings S
No pings yet today
No pings yesterday
Amazonbot 8 Perplexity 6 Google 2 Ahrefs 2 Unknown AI 2 ChatGPT 2
crawler 20 crawler_json 2
DEV INTEL Tools & Severity
🟡 Medium ⚙ Fix effort: Medium
⚡ Quick Fix
Start small: pick the most-misused primitive in your codebase (usually email, money, or a status string) and wrap it in a value object — you'll immediately catch bugs at construction time
📦 Applies To
any web cli queue-worker
🔗 Prerequisites
🔍 Detection Hints
Email validation duplicated in 10 places; float for money causing rounding bugs; string status compared across multiple files with no enum
Auto-detectable: ✗ No phpstan phpmd
⚠ Related Problems
🤖 AI Agent
Confidence: Medium False Positives: Medium ✗ Manual fix Fix: High Context: File Tests: Update

✓ schema.org compliant