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

Primitive Obsession

Code Quality PHP 5.0+ Intermediate
debt(d7/e7/b5/t5)
d7 Detectability Operational debt — how invisible misuse is to your safety net

Closest to 'only careful code review or runtime testing' (d7). PHPStan/Psalm don't flag primitive obsession directly — they accept string/int parameters happily. Detection requires human judgment about whether a primitive represents a domain concept.

e7 Effort Remediation debt — work required to fix once spotted

Closest to 'cross-cutting refactor across the codebase' (e7). The quick_fix says replace a cluster of primitives with a Value Object, but in practice every call site, function signature, serialization boundary, and storage layer that touched the primitive must change — emails/money/IDs travel everywhere.

b5 Burden Structural debt — long-term weight of choosing wrong

Closest to 'persistent productivity tax' (b5). applies_to spans web/cli/queue contexts; raw primitives for domain concepts force scattered validation and defensive checks across many work streams, but the system can still function and be incrementally improved.

t5 Trap Cognitive debt — how counter-intuitive correct behaviour is

Closest to 'notable trap (documented gotcha)' (t5). Per the misconception, devs believe primitives are simpler — but learn through bugs (passing userId where productId expected, float money rounding) that value objects prevent whole classes of errors. A well-known smell most experienced devs eventually internalize.

About DEBT scoring →

Also Known As

primitive overuse stringly typed value type smell

TL;DR

Using raw strings, ints, and arrays to represent domain concepts instead of small dedicated value objects.

Explanation

Primitive obsession is using primitive types (string, int, bool, array) for concepts that deserve their own class — e.g. representing a phone number as a plain string, a money amount as a float, or a coordinate pair as two separate variables. Primitives have no domain validation, no domain-specific methods, and no documentation of their meaning. Introducing small value objects (PhoneNumber, Money, Coordinate) gives the type a name, centralises validation, and makes illegal states unrepresentable.

Common Misconception

Using primitives for everything is simpler than creating wrapper classes. A string for an email address cannot enforce format, a float for money cannot prevent rounding errors, and an int for a user ID can be accidentally passed where a product ID is expected — value objects eliminate these classes of bugs.

Why It Matters

Using raw strings and integers for domain concepts (email, money, coordinates) means validation and behaviour are scattered everywhere — wrapping them in value objects centralises rules and makes types self-documenting.

Common Mistakes

  • Passing $email as a string everywhere instead of an Email value object that validates on construction.
  • Using int for money — floating point errors and currency handling belong in a Money class.
  • Not noticing when a cluster of primitives always travels together — that is a data clump and a value object candidate.
  • Creating value objects without making them immutable — a mutable Email can change to invalid state after construction.

Code Examples

✗ Vulnerable
function registerUser(string $email, string $role, int $age): void {
    if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
        throw new \InvalidArgumentException('Bad email');
    }
    // $email, $role, $age scattered everywhere as raw strings/ints
}
✓ Fixed
readonly class Email {
    public function __construct(public string $value) {
        if (!filter_var($value, FILTER_VALIDATE_EMAIL)) {
            throw new \InvalidArgumentException("Invalid email: $value");
        }
    }
}

function registerUser(Email $email, Role $role, Age $age): void {
    // types carry their own validation — impossible to pass invalid data
}

Added 15 Mar 2026
Edited 22 Mar 2026
Views 57
Rate this term
No ratings yet
🤖 AI Guestbook educational data only
| |
Last 30 days
0 pings T 0 pings W 0 pings T 0 pings F 1 ping S 0 pings S 0 pings M 1 ping T 0 pings W 0 pings T 1 ping F 5 pings S 2 pings S 1 ping M 0 pings T 3 pings W 1 ping T 0 pings F 0 pings S 0 pings S 0 pings M 1 ping T 0 pings W 0 pings T 0 pings F 0 pings S 0 pings S 1 ping M 0 pings T 0 pings W
No pings yet today
No pings yesterday
Scrapy 11 Amazonbot 10 Perplexity 6 Ahrefs 5 ChatGPT 4 SEMrush 3 Google 2 Unknown AI 2 Claude 2 PetalBot 1
crawler 43 crawler_json 3
DEV INTEL Tools & Severity
🟢 Low ⚙ Fix effort: Medium
⚡ Quick Fix
Replace a cluster of related primitives (string $email, string $currency, int $amount) with a Value Object that encapsulates validation and behaviour
📦 Applies To
PHP 5.0+ web cli queue-worker
🔗 Prerequisites
🔍 Detection Hints
Function with 5+ string/int parameters representing domain concepts or repeated format-validation of same field
Auto-detectable: ✗ No phpstan psalm
⚠ Related Problems
🤖 AI Agent
Confidence: Medium False Positives: High ✗ Manual fix Fix: High Context: File Tests: Update


✓ schema.org compliant