If you are upgrading a legacy codebase to PHP 8.2, your logs are likely flooding with this message:
Deprecated: Creation of dynamic property ClassName::$propertyName is deprecated
This is not a bug in the PHP interpreter. It is a deliberate language change to enforce stricter object definitions. In PHP 9.0, this deprecation notice will become a fatal ErrorException, crashing your application.
Here is the technical breakdown of why this is happening and the three specific strategies to resolve it.
The Root Cause: Object Shapes and Optimization
Historically, PHP allowed developers to assign values to undeclared properties on any object. While flexible, this behavior incurs significant technical debt:
- Performance: PHP engines (Zend) rely on "object shapes" (hidden classes) to optimize property access. When you dynamically add properties, you mutate the object's shape at runtime, forcing the engine to de-optimize and fall back to slower hash-table lookups.
- Maintainability: Dynamic properties defeat static analysis tools (PHPStan, Psalm) and IDE autocompletion, making refactoring risky.
- Correctness: It allows silent failure on typos (e.g., assigning to
$user->nmaeinstead of$user->name).
PHP 8.2 restricts this behavior to push developers toward explicit schema definitions.
Solution 1: Explicit Property Declaration (Recommended)
The most robust fix is to strictly define your class schema. This is the "Principal Engineer" approach: it fixes the deprecation, documents the code, improves performance, and enables type safety.
The Legacy Code (Fails in 8.2)
<?php
class UserEntity {
public function __construct(int $id, string $name) {
// Triggers Deprecation in 8.2
$this->id = $id;
$this->name = $name;
}
}
$user = new UserEntity(1, 'Alice');
$user->isAdmin = true; // Triggers Deprecation in 8.2
The Modern Fix
Refactor the class to declare properties. Use typed properties (available since PHP 7.4) and readonly (PHP 8.1+) where immutable state is required.
<?php
declare(strict_types=1);
class UserEntity {
public int $id;
public string $name;
public bool $isAdmin = false; // Default value
public function __construct(int $id, string $name) {
$this->id = $id;
$this->name = $name;
}
}
$user = new UserEntity(1, 'Alice');
$user->isAdmin = true; // Valid
Why this works: The properties exist in the class definition (the VTable). The engine knows the memory offset for these properties at compile time.
Solution 2: The #[AllowDynamicProperties] Attribute
If you are maintaining a massive legacy codebase (e.g., Magento 1, old WordPress plugins) or a library that acts as a generic data container, refactoring every class may be impossible.
PHP 8.2 introduces the #[AllowDynamicProperties] attribute. This signals to the engine that strict shape enforcement should be disabled for this specific class.
The Fix
Simply add the attribute just above the class definition.
<?php
declare(strict_types=1);
// This attribute opts-out of the deprecation notice
#[AllowDynamicProperties]
class LegacyConfigContainer {
public function __construct(array $config) {
foreach ($config as $key => $value) {
// This is now allowed in 8.2 without warnings
$this->{$key} = $value;
}
}
}
$config = new LegacyConfigContainer(['db_host' => 'localhost', 'retries' => 3]);
echo $config->db_host; // Output: localhost
Note on Inheritance: Child classes automatically inherit this behavior. You do not need to re-declare the attribute on classes extending LegacyConfigContainer.
Solution 3: Using stdClass or __get/__set
There are specific use cases—such as parsing arbitrary JSON responses or building fluent builders—where dynamic properties are a feature, not a bug.
Option A: Use stdClass
The built-in stdClass is explicitly exempt from this deprecation. If your object has no methods and is purely a data transfer object (DTO) for unstructured data, cast it or use stdClass.
<?php
$payload = json_decode('{"status": "ok", "meta": {"count": 5}}');
// json_decode returns stdClass by default, which allows dynamic properties
$payload->timestamp = time(); // Valid in 8.2+
Option B: Magic Methods
If you need logic attached to dynamic data, implement __get and __set. Classes implementing these magic methods are exempt from the deprecation because the property assignment is intercepted by logic rather than the object's internal structure map.
<?php
declare(strict_types=1);
class DynamicRegistry {
private array $storage = [];
public function __set(string $name, mixed $value): void {
$this->storage[$name] = $value;
}
public function __get(string $name): mixed {
return $this->storage[$name] ?? null;
}
}
$registry = new DynamicRegistry();
// Calls __set(), does not trigger dynamic property deprecation
$registry->apiKey = '123-456';
Summary
- Audit: Run your test suite with
E_DEPRECATEDenabled to find offenders. - Refactor (Best): Add typed property declarations to your classes.
- Patch (Legacy): Apply
#[AllowDynamicProperties]to classes that cannot be refactored. - Architect: Use
stdClassor Magic Methods for truly dynamic data structures.
Fixing this now is critical. Ignoring these notices effectively puts an expiration date on your application matching the release of PHP 9.0.