Typed Class Constants (PHP 8.3)
debt(d1/e1/b3/t3)
Closest to 'caught instantly (compiler/syntax error)' (d1). Using typed constants in PHP < 8.3 produces a parse error immediately. In PHP 8.3+, a type mismatch at definition time throws a TypeError when the class loads — runtime but immediate, not silent in production. The detection_hints.tools field is not specified, but the quick_fix notes PHPStan and Psalm understand this syntax, reinforcing that even static analysis catches it early.
Closest to 'one-line patch or single-call swap' (e1). The quick_fix is explicit: add a type annotation between 'const' and the constant name — a single syntactic change with no other modifications needed. No refactoring across files is required.
Closest to 'localised tax' (b3). The feature applies to web and CLI contexts in PHP 8.3+, scoped to class/interface constants. It imposes a version constraint (php_min: 8.3) on any codebase adopting it, and the common_mistakes note about inherited classes and LSP compliance adds a small ongoing tax when maintaining class hierarchies, but the rest of the codebase is largely unaffected.
Closest to 'minor surprise (one edge case)' (t3). The misconception field states developers may believe typed constants are re-checked at every usage point, but enforcement only happens at definition/load time. This is a mild surprise rather than a catastrophic one — the practical impact is limited, and the int-to-float coercion edge case (3 coerced to 3.0) is another minor gotcha. Not a serious contradiction with how similar concepts work elsewhere.
Also Known As
TL;DR
Explanation
Before PHP 8.3, class constants were untyped — any value could be assigned regardless of what the code expected. PHP 8.3 allows a type annotation on constants: 'const string VERSION = '1.0.0';'. The declared type is enforced at compile time (by static analysers) and at runtime when the class is loaded. Types follow the same rules as property types: scalar types, union types, intersection types, and nullable types are all supported. Interface constants can declare types that implementing classes must honour. This is especially valuable in enums where constants often carry semantic meaning.
Common Misconception
Why It Matters
Common Mistakes
- Using typed constants in PHP < 8.3 — they produce a parse error; add a version guard or use PHPStan @var annotations as a pre-8.3 alternative.
- Declaring const float PI = 3.14159 and assigning an integer literal — PHP will coerce 3 to 3.0, but be explicit to avoid confusion.
- Forgetting that inherited classes can override constants but must maintain type compatibility — narrowing the type in a subclass is a Liskov Substitution violation.
- Not annotating interface constants — these benefit most from typing because implementors need to know what type is expected.
Code Examples
<?php
// ❌ Untyped constant — wrong type silently accepted
class Config
{
const TIMEOUT = '30'; // Should be int, but string slips through
const MAX_RETRY = 3.5; // Should be int, float goes unnoticed
}
echo Config::TIMEOUT + 10; // PHP coerces '30' — works, but fragile
<?php
// ✅ Typed constants — PHP 8.3+
class Config
{
const int TIMEOUT = 30; // TypeError if assigned '30' (string)
const int MAX_RETRY = 3; // TypeError if assigned 3.5 (float)
const string VERSION = '1.0.0';
const array|null HEADERS = null;
}
// Interface constants can also be typed
interface Statusable
{
const string STATUS_ACTIVE = 'active';
const string STATUS_INACTIVE = 'inactive';
}