Skip to main content

Fixing "Deprecated: Creation of dynamic property" in Legacy PHP 8.2 Apps

 If you maintain a legacy PHP codebase or a WordPress plugin that hasn't seen a major refactor since PHP 7.4, your error logs are likely flooding with this message after upgrading to PHP 8.2:

Deprecated: Creation of dynamic property ClassName::$propertyName is deprecated

This is not a false alarm. While it is currently a deprecation warning, PHP 9.0 will escalate this to a fatal ErrorException. The era of treating PHP objects as "fancy arrays" where you can attach arbitrary data at runtime is over.

The Root Cause: Why PHP Changed

Historically, PHP allowed developers to assign values to undeclared properties on any object. Under the hood, the Zend Engine would silently create a dynamic property map (a HashTable) attached to the object instance to store these values.

While convenient, this behavior caused two major issues:

  1. Performance: Accessing declared properties is optimized via strict memory offsets. Accessing dynamic properties requires a slower hash table lookup.
  2. Fragility: It effectively disables typo protection. Writing $user->nmae = 'John' would silently succeed instead of throwing an error, leading to elusive bugs.

In PHP 8.2, the internal handling of classes was tightened to enforce the defined shape of an object. If a property does not exist in the class definition, the engine now flags it.

The Fixes: From Quick Patch to Architectural Correction

There are three ways to solve this, depending on the context of the code and your refactoring budget.

Scenario: The Legacy "Bag of Data"

Consider this common pattern found in older ORMs or API wrappers:

<?php

namespace LegacyApp\Models;

class UserProfile {
    public function __construct(array $data) {
        foreach ($data as $key => $value) {
            // DEPRECATED in PHP 8.2
            $this->{$key} = $value;
        }
    }
}

$user = new UserProfile(['id' => 1, 'role' => 'admin']);
// Warning: Creation of dynamic property LegacyApp\Models\UserProfile::$id is deprecated

Solution 1: Explicit Property Declaration (The Best Practice)

The strictly correct solution is to define the properties. This improves memory usage, allows for type safety, and enables static analysis tools (like PHPStan) to validate your code.

Use PHP 8.1+ readonly features or standard typed properties:

<?php

namespace LegacyApp\Models;

class UserProfile {
    public int $id;
    public string $role;
    // Nullable type for optional fields
    public ?string $bio = null;

    public function __construct(array $data) {
        $this->id = $data['id'];
        $this->role = $data['role'];
        $this->bio = $data['bio'] ?? null;
    }
}

$user = new UserProfile(['id' => 1, 'role' => 'admin']);
// No warnings. Fully typed. Optimized memory footprint.

Solution 2: The #[AllowDynamicProperties] Attribute (The Hotfix)

If you are dealing with a massive "God Class," a third-party library you cannot deeply refactor, or a class specifically designed to be dynamic (like a configuration object), use the built-in attribute.

This tells the PHP engine explicitly: "I intend for this class to hold dynamic data."

<?php

namespace LegacyApp\Config;

// 1. Import the attribute (optional but good practice for clarity)
use AllowDynamicProperties;

// 2. Apply the attribute to the class
#[AllowDynamicProperties]
class PluginConfig {
    public function setOption(string $key, mixed $value): void {
        $this->{$key} = $value;
    }
}

$config = new PluginConfig();
$config->debugMode = true; // Valid. No deprecation warning.

Note: This attribute inherits. If a parent class has #[AllowDynamicProperties], all child classes automatically allow dynamic properties as well.

Solution 3: Using stdClass or WeakMap (The Architectural Shift)

If your class is strictly a data container with no methods, it shouldn't be a custom class at all. PHP's built-in stdClass is exempt from the dynamic property deprecation.

Refactoring a DTO (Data Transfer Object):

<?php

// BEFORE: Custom class acting as a generic container
// class ResponseData {} 
// $r = new ResponseData(); $r->status = 200; // Deprecated

// AFTER: Use stdClass directly
$response = new \stdClass();
$response->status = 200;
$response->payload = ['foo' => 'bar'];

// OR: Use modern anonymous classes if you need on-the-fly structure
$response = new class {
    public int $status = 200;
    public array $payload = [];
};

Why This Matters

Applying #[AllowDynamicProperties] is a valid short-term strategy, but it should be treated as a technical debt marker.

When you declare properties explicitly (Solution 1), you gain:

  1. Opcache Optimization: The compiler knows the exact size and shape of the object in memory.
  2. IDE Autocompletion: IntelliSense works immediately.
  3. Typos become Fatal: $user->emial = '...' throws an error immediately during development, rather than silently failing and causing data loss in production.

Conclusion

PHP is moving toward a stricter type system. The dynamic property deprecation is a signal to stop treating objects as unstructured hashmaps.

  1. Audit: Use static analysis tools like PHPStan (level 2 or higher) to find these usages before they hit production logs.
  2. Patch: Use #[AllowDynamicProperties] for external libraries or legacy code you cannot rewrite immediately.
  3. Refactor: Explicitly declare properties for your core domain logic.