Accessing Undeclared Properties (PHP 8.2+)
debt(d5/e3/b3/t7)
Closest to 'specialist tool catches it' (d5). The detection_hints list phpstan and psalm as the tools that catch this, and the common_mistakes note that PHPStan level 6+ is required. These are specialist static analysis tools, not default linters or compiler errors. In production the deprecation notice is an E_DEPRECATED warning that may be swallowed depending on error reporting configuration, making it silent without tooling.
Closest to 'simple parameterised fix' (e3). The quick_fix indicates declaring all class properties explicitly and optionally adding #[AllowDynamicProperties] as a temporary measure. This is more than a one-line swap (you must audit and declare properties across a class), but it is still localised within a single class at a time rather than spanning multiple files as a cross-cutting refactor.
Closest to 'localised tax' (b3). The issue applies per-class where dynamic properties are used. The burden falls on the maintainer of each affected class to declare properties or add the attribute. It applies broadly across web, cli, and queue-worker contexts but is addressed class by class rather than imposing a system-wide architectural weight. It does not impose a persistent tax on unrelated code paths.
Closest to 'serious trap' (t7). The misconception field directly states that developers believe dynamic properties are removed in PHP 8.2, when in fact they are only deprecated (E_DEPRECATED) with the fatal Error arriving in PHP 9. This contradicts the common expectation that a 'deprecated' feature from a major version is immediately fatal, and the silent typo-creates-new-property behaviour (from common_mistakes) further contradicts how property assignment is expected to work in strictly-typed languages.
TL;DR
Explanation
Before PHP 8.2, classes silently accepted any property assignment ($obj->foo = 'bar' even if $foo wasn't declared). PHP 8.2 deprecated this with E_DEPRECATED. PHP 9 will throw an Error. Exceptions: stdClass and objects using #[AllowDynamicProperties] still allow dynamic properties. __get()/__set() magic methods also still work. The migration path: run static analysis to find all dynamic property access, add explicit typed declarations, or add #[AllowDynamicProperties] to legacy classes as a transitional step.
Common Misconception
Why It Matters
Common Mistakes
- Setting a property with a typo — creates a new dynamic property silently instead of throwing.
- Not adding #[AllowDynamicProperties] to legacy value objects during migration.
- Not running PHPStan level 6+ which flags dynamic property access.
Code Examples
class User {
public string $name;
}
$u = new User();
$u->nmae = 'Paul'; // Typo: creates dynamic property 'nmae', $name stays unset
// PHP 8.2: Deprecated, PHP 9: Fatal Error
class User {
public string $name;
public string $email;
// All properties explicitly declared — typos caught at PHP level
}
// For legacy classes needing time to migrate:
#[\AllowDynamicProperties]
class LegacyDto {
// Allowed for now, migrate later
}