In PHP 8.2 and later, what happens when code assigns an undeclared property on an object, and when is #[AllowDynamicProperties] justified?
answer
- an assignment to an undeclared name
- E_DEPRECATED since 8.2
- stdClass and subclasses are exempt
- the attribute is inherited by children
- readonly classes throw instead
basics
~20 sSince PHP 8.2, assigning an undeclared property creates it but emits E_DEPRECATED: Creation of dynamic property is deprecated. Declare the property instead; #[AllowDynamicProperties] is a last resort for legacy classes that truly need ad-hoc fields.
solid answer
~50 sAssigning a property that the class does not declare, `$booking->promoCode = 'SUMMER'`, creates a **dynamic property** on that one instance. Since **PHP 8.2** that emits `Deprecated: Creation of dynamic property FlightBooking::$promoCode is deprecated`; the property is still created. Most of these are typos or leftovers, so the fix is to **declare** the property. Exempt: `stdClass` (and its subclasses), classes marked `#[\AllowDynamicProperties]` and their children, since the effect is inherited, and classes whose `__set()` stores unknown names somewhere else. In classes that forbid them outright, readonly classes and many internal classes, the assignment throws `Error: Cannot create dynamic property ...`. The attribute is justified for legacy code you cannot refactor yet, or objects that really are open-ended bags; for attaching data to objects you do not own, a map keyed by the object is cleaner. A readonly class cannot use the attribute.
code
php · 18 lines<?php
declare(strict_types=1);
final class FlightBooking
{
public string $passengerName = '';
}
$b = new FlightBooking();
$b->pasengerName = 'Ada Lovelace';
// Deprecated: Creation of dynamic property FlightBooking::$pasengerName is deprecated
#[\AllowDynamicProperties]
class LegacyRow {}
class LegacyBookingRow extends LegacyRow {}
$row = new LegacyBookingRow();
$row->anything = 1; // silent: the attribute's effect is inheritedgo deeper
Recall that since PHP 8.2 assigning a property the class does not declare triggers a deprecation notice, and that declaring the property fixes it.
Explain which classes are exempt, that the attribute's effect is inherited, and that readonly and many internal classes throw instead.
Plan a migration that treats the notices as a typo detector, prefers declared properties or explicit maps, and limits the attribute to legacy hotspots.
Set a policy for dynamic properties in shared codebases, including how CI treats deprecations and when legacy opt-ins must be removed.
## What a dynamic property is In PHP an object can gain a property just by assigning it: if `FlightBooking` declares no `$promoCode`, then `$booking->promoCode = 'SUMMER'` creates one on that single instance. Other instances of the class do not have it, tools cannot see it, and a typo such as `$booking->pasengerName = ...` silently creates a new property instead of failing. ## The PHP 8.2 deprecation Since **PHP 8.2**, creating a dynamic property emits an `E_DEPRECATED` notice: ``` Deprecated: Creation of dynamic property FlightBooking::$promoCode is deprecated ``` The assignment still works; the notice is a migration signal. Reading or writing a dynamic property that already exists does not repeat the notice, since only **creation** is deprecated. | Class | Assigning an undeclared property | |---|---| | ordinary user class | created, with the deprecation notice | | `stdClass` and its subclasses | created silently | | class with `#[\AllowDynamicProperties]`, or a child of one | created silently | | class whose `__set()` handles the name | `__set()` runs; nothing is created unless it assigns one itself | | `readonly class` | `Error: Cannot create dynamic property ...` | | many internal classes | `Error: Cannot create dynamic property ...` | ## Fixing the notices Work through them in this order: 1. **Declare the property**, with a type and a default where one makes sense. This fixes the large majority, and it is what the deprecation is pushing towards. 2. **Find the typos.** A notice for `$pasengerName` on a class that declares `$passengerName` is a real bug the deprecation just found. 3. **Use a real map** when the set of keys is open-ended: an `array $extra = []` property, or `__get()`/`__set()` that store into it. 4. **Attach data to objects you do not own** with a map keyed by the object, such as `WeakMap`, instead of adding properties to someone else's instances. 5. **Add `#[\AllowDynamicProperties]`** only where none of the above is feasible yet, typically in legacy code that relies on the behaviour across many call sites. ## The attribute - It is a class-level attribute: `#[\AllowDynamicProperties] class LegacyRow {}`. - Its **effect is inherited**: child classes allow dynamic properties without repeating the attribute, even though attributes themselves are not inherited. - It **cannot** be applied to a readonly class (`Cannot apply #[AllowDynamicProperties] to readonly class ...`). - `stdClass` carries it, which is why `new stdClass` still accepts arbitrary properties. ## Why the language moved away from them - **Typos become bugs**: a misspelt property name creates data nobody reads. - **No types or tooling**: IDEs and static analysers cannot know the shape of the object. - **Surprising sharing**: different instances of one class have different sets of properties, which breaks assumptions in serializers and mappers. Declared properties, especially typed ones, remove all three problems. ## Finding them before users do - Run the test suite with deprecations reported and treat them as failures in CI, so each notice points at a line. - Static analysers report assignments to undeclared properties without running the code, which also covers paths the tests miss. - In production logs the message names the class and the property (`FlightBooking::$promoCode`), which is usually enough to find the missing declaration. Deprecations mark behaviour the language intends to remove, so fixing them early avoids a forced migration later. ## Summary Since PHP 8.2, creating an undeclared property is deprecated unless the class is `stdClass`, carries `#[\AllowDynamicProperties]` (directly or through a parent) or handles it in `__set()`; readonly classes and many internal classes throw instead. Declare properties, and treat the attribute as a last resort.
- Is `#[\AllowDynamicProperties]` needed on a child class whose parent already has it?No. Attributes are not inherited as such, but this one's effect is: the engine copies the permission to child classes, so subclasses of a marked class accept dynamic properties silently without repeating the attribute.
- What happens if a readonly class receives an undeclared property assignment?It throws `Error: Cannot create dynamic property ...` instead of a deprecation notice, because readonly classes forbid dynamic properties outright. Adding `#[\AllowDynamicProperties]` is not an option either; the engine rejects the attribute on a readonly class.
saying these in an interview costs you the question
- Since PHP 8.2 assigning an undeclared property throws an Error in every class.
- The deprecation also fires every time an existing dynamic property is read.
- Child classes must repeat #[AllowDynamicProperties] to keep the behaviour.
- Adding the attribute everywhere is the recommended fix for the deprecation.
- stdClass needs #[AllowDynamicProperties] added by the user to accept properties.