Typed Properties in PHP: Catching State Bugs at Assignment
Typed properties move type validation from business logic to the PHP engine, catching bad state earlier and helping static analyzers find bugs before runtime.
14 Aug 2025, 09:01 UTC

Untyped class properties let bad data sit quietly until it causes a failure far from where it was introduced. Typed properties introduced in PHP 7.4 move that check to the point of assignment, making object state self-enforcing and easier for static analysis tools to reason about.
The problem is late failure in stateful objects
In legacy PHP a property can hold any value at any time. A DTO or model may receive a string where an integer is expected, and the error only surfaces later during arithmetic or persistence. The distance between cause and symptom makes debugging expensive.
The thesis is simple: declare the type on the property itself so the engine rejects invalid assignments immediately and tools can verify usage without running the code.
What runtime enforcement actually changes
A typed property combines visibility with a type declaration. The engine validates on write and on read. Reading a typed property before it is initialized throws an Error, which differs from older behavior where an uninitialized property read returned null. Nullable types must be explicit with ?.
Typed properties work with visibility modifiers and are compatible with inheritance, allowing gradual adoption. They also expose their declared type via reflection, which frameworks can use for validation and mapping.
Worked example: a safe DTO
The following class shows explicit types, nullable handling, and initialization in the constructor. Run this in a PHP 7.4+ interpreter with read access to the file.
class OrderItem {
public int $id;
public string $sku;
public ?string $note;
public function __construct(int $id, string $sku, ?string $note = null) {
$this->id = $id;
$this->sku = $sku;
$this->note = $note;
}
}
With this declaration an incompatible assignment to $id or $sku triggers a TypeError at assignment time. The nullable ?string allows $note to be null or a string, but the property must still be initialized before read.
A practical check is reflection. Using ReflectionProperty::getType() you can inspect declared types at runtime to confirm they are present and to drive generic mapping code.
Trade-offs and limits to plan for
Typed properties forbid dynamic properties. Assigning a property that was not declared results in an error in strict contexts. This forces explicit class design but can break code that relied on dynamic bags.
Inheritance is constrained. A child class cannot widen the type of an inherited property. Changing a parent property type can break children that rely on the original contract.
Uninitialized access is a common migration pain point. Existing code that reads a property before setting it will now throw instead of returning null. Guard this by initializing in constructors or using nullable types where absence is valid.
Actionable rollout
Start with DTOs and value objects where data integrity matters most. Add types to new classes first, then migrate hot paths. Run PHPStan or Psalm at increasing levels to find implicit assumptions. Ensure the runtime is PHP 7.4 or newer before deploying typed declarations. For mixed-version environments, keep type checks behind version guards or use runtime validation as a fallback.
Typed properties do not replace validation of external input, but they create a clear contract for internal state. That contract reduces silent drift and makes refactors safer.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.