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

readonly Classes (PHP 8.2)

PHP PHP 8.2+ Beginner
debt(d5/e3/b3/t5)
d5 Detectability Operational debt — how invisible misuse is to your safety net

Closest to 'specialist tool catches it' (d5). The detection_hints list rector and phpstan — both specialist static-analysis tools — as the primary means of finding misuse (e.g. attempting to extend a non-readonly class, adding static properties, or misunderstanding shallow immutability). These issues won't be flagged by a default linter but will be caught by phpstan type-checking or rector migrations.

e3 Effort Remediation debt — work required to fix once spotted

Closest to 'simple parameterised fix' (e3). The quick_fix instructs converting value objects/DTOs to readonly classes and removing individual readonly keywords — a straightforward, pattern-based refactor typically contained within one or a small set of related files. It's more than a one-line patch but doesn't span the whole codebase.

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

Closest to 'localised tax' (b3). The applies_to scope covers web, cli, and queue-worker contexts, but the choice is confined to the specific value objects and DTOs that use readonly classes. The rest of the codebase is unaffected; it's a localised structural choice per class, not a gravitational architectural decision.

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

Closest to 'notable trap' (t5). The misconception field explicitly states developers assume deep immutability — but readonly only prevents property reassignment, not mutation of nested objects (shallow immutability). This is a well-documented gotcha that many developers eventually discover, plus the additional surprises of inability to extend non-readonly classes and no static properties.

About DEBT scoring →

TL;DR

PHP 8.2 readonly classes make all promoted properties readonly automatically — the cleanest way to define immutable value objects and DTOs.

Explanation

readonly class Money { } makes all declared properties implicitly readonly — no need to mark each one. Rules: all properties must be typed. Cannot have untyped or static properties. Allows constructor promotion. Can extend readonly classes. Cannot extend non-readonly classes (or vice versa — asymmetric readonly inheritance breaks the contract). readonly classes implement all readonly property restrictions: write-once, no unset, requires type. PHP 8.3: readonly properties can be modified in __clone() for copy-with patterns. Ideal for: DTOs, value objects, request/response objects, command/query objects.

Common Misconception

readonly classes prevent all mutation — readonly prevents property writes, but objects inside properties can still be mutated (shallow immutability). Use clone + new values for copy-with.

Why It Matters

readonly classes eliminate property-by-property readonly annotation while enforcing immutability for entire value object families — cleaner and safer.

Common Mistakes

  • Trying to extend a non-readonly class from readonly class — fatal error.
  • Adding static properties to readonly class — not allowed.
  • Assuming deep immutability — nested objects are still mutable.

Code Examples

✗ Vulnerable
class UserDTO {
    public function __construct(
        public readonly int $id,
        public readonly string $name,
        public readonly string $email,
    ) {}
}
✓ Fixed
// PHP 8.2 — readonly class:
readonly class UserDTO {
    public function __construct(
        public int $id,
        public string $name,
        public string $email,
    ) {}

    // PHP 8.3 copy-with:
    public function withName(string $name): static {
        $clone = clone $this;
        $clone->name = $name; // Only in __clone in 8.3
        return $clone;
    }
}

Added 23 Mar 2026
Views 124
Rate this term
No ratings yet
🤖 AI Guestbook educational data only
| |
Last 30 days
0 pings T 0 pings W 0 pings T 1 ping F 11 pings S 8 pings S 14 pings M 0 pings T 2 pings W 4 pings T 3 pings F 3 pings S 2 pings S 1 ping M 4 pings T 2 pings W 1 ping T 5 pings F 3 pings S 0 pings S 1 ping M 2 pings T 1 ping W 0 pings T 2 pings F 3 pings S 2 pings S 1 ping M 2 pings T 0 pings W
No pings yet today
ChatGPT 2
ChatGPT 74 Amazonbot 9 Google 6 Scrapy 6 Perplexity 4 Unknown AI 3 Ahrefs 3 SEMrush 3 Claude 2 Meta AI 1 PetalBot 1
crawler 106 crawler_json 4 pre-tracking 2
DEV INTEL Tools & Severity
🔵 Info ⚙ Fix effort: Low
⚡ Quick Fix
Convert value objects and DTOs to readonly classes. Remove individual readonly keywords — the class declaration covers all properties.
📦 Applies To
PHP 8.2+ web cli queue-worker
🔗 Prerequisites
🔍 Detection Hints
class.*public readonly|readonly class
Auto-detectable: ✓ Yes rector phpstan
🤖 AI Agent
Confidence: Low False Positives: Medium ✓ Auto-fixable Fix: Low Context: Class


✓ schema.org compliant