Skip to content

Emergency patch, £245 fixed →

“Creation of Dynamic Property Is Deprecated”: How to Fix It in PHP 8.2+

Why PHP 8.2 and later warn “Creation of dynamic property is deprecated”, five ways to fix it, and how to find every case in a codebase with PHPStan and Rector.

6 min read

The short version

  • Since PHP 8.2, assigning to a property a class never declared triggers “Creation of dynamic property … is deprecated”.
  • Your code still works today, but this is planned to become a fatal Error in PHP 9.
  • The fix is almost always to declare the property. Rector can do most of it for you.
  • stdClass, Laravel models and classes with __get()/__set() are not affected.

The message

If your server has moved to PHP 8.2 or later, your logs (or worse, your pages) may be full of lines like this:

Deprecated: Creation of dynamic property App\Order::$total is deprecated
in /var/www/src/Order.php on line 14

It means the code at that line wrote to $this->total (or $order->total) on an object whose class doesn’t declare a $total property. PHP has always quietly created the property on the fly. Since PHP 8.2 it still does, but now warns you first.

Why PHP deprecated dynamic properties

Silently creating properties hides bugs. A typo like $this->emial doesn’t fail: it creates a brand-new property, and the real $email is never set. Undeclared properties also have no type, can’t be checked by static analysis tools and are invisible to anyone reading the class.

The deprecate dynamic properties RFC was accepted for PHP 8.2 with a two-step plan: a deprecation notice now, then an Error exception in PHP 9. Code that relies on dynamic properties is on a clock, even though nothing breaks today.

What is and isn’t affected

ClassDeprecation notice?
Ordinary class writing to an undeclared propertyYes
stdClass (including json_decode() objects)No
Class with __get() and __set() (e.g. Laravel Eloquent models)No
Class marked #[\AllowDynamicProperties], and its subclassesNo
Reading an undeclared propertyNo new notice (still the usual “Undefined property” warning)

How to fix it

Here’s the typical cause:

class Order
{
    public function __construct(public int $id) {}

    public function calculate(): void
    {
        $this->total = 0; // $total was never declared: deprecated since PHP 8.2
    }
}

1. Declare the property (the right fix 90% of the time)

class Order
{
    private int $total = 0; // declared, typed and with a default

    public function __construct(public int $id) {}

    public function calculate(): void
    {
        $this->total = 0;
    }
}

Add a type and a sensible default while you’re there. Pick the visibility from how it’s used: if only the class itself touches it, make it private.

2. Check it isn’t a typo

A surprising number of these notices are real bugs that have been there for years:

class Customer
{
    public string $email = '';

    public function update(array $data): void
    {
        $this->emial = $data['email']; // typo: silently created a new property before PHP 8.2
    }
}

Here the fix is to correct the name, not declare $emial. Always look at the property name before adding it to the class.

3. Use an array for genuinely arbitrary data

If a class stores keys that aren’t known in advance (settings, metadata, rows from a dynamic query), keep them in one declared array instead of as properties:

class Settings
{
    /** @var array<string, mixed> */
    private array $values = [];

    public function set(string $key, mixed $value): void
    {
        $this->values[$key] = $value;
    }

    public function get(string $key, mixed $default = null): mixed
    {
        return $this->values[$key] ?? $default;
    }
}

4. Use a WeakMap for objects you don’t own

Some code “tags” third-party objects by setting extra properties on them. You can’t declare properties on a library’s class, so store the data alongside the object instead. A WeakMap (PHP 8.0+) frees the entry automatically when the object goes away:

// Before: tagging an object from a library you don't control
$request->startedAt = microtime(true);

// After: keep the data alongside it instead
$startedAt = new WeakMap();
$startedAt[$request] = microtime(true);

5. #[\AllowDynamicProperties] as a deliberate exception

For a class that genuinely needs arbitrary properties, or a legacy class you can’t refactor yet, mark it explicitly:

#[\AllowDynamicProperties]
class LegacyRecord
{
    // Opts this class (and its subclasses) out of the deprecation
}

It’s supported and future-proof, but it opts out of the safety the deprecation gives you. Use it for a handful of named classes, not as a search-and-replace across the codebase.

Finding every dynamic property in a codebase

Fixing notices one at a time as they appear in the logs only finds the code paths that happened to run. To find them all:

Count what’s already in your logs

# Every class and property that triggered the notice, most frequent first
grep -o 'Creation of dynamic property [^ ]*' /var/log/php*-fpm.log \
    | sort | uniq -c | sort -rn

Adjust the path to wherever your PHP errors are logged. This gives a quick, prioritised list from real traffic.

Run PHPStan

PHPStan reports “Access to an undefined property” even at its lowest level, so it finds dynamic properties in code that never ran in production:

# phpstan.neon: undefined properties are reported from level 0
parameters:
    level: 0
    paths:
        - src

Let Rector declare them

Rector’s CompleteDynamicPropertiesRector adds the missing property declarations for you, inferring types where it can:

<?php
// rector.php
use Rector\CodeQuality\Rector\Class_\CompleteDynamicPropertiesRector;
use Rector\Config\RectorConfig;

return RectorConfig::configure()
    ->withPaths([__DIR__ . '/src'])
    ->withRules([CompleteDynamicPropertiesRector::class]);

Run it with --dry-run first and review the diff. Rector will happily declare $emial too, so the typo check in step 2 is still a human job.

WordPress, plugins and third-party code

On WordPress sites, most of these notices come from plugins and themes rather than core. Update everything first. If a plugin still triggers the notice on its latest version, check when it was last updated: an abandoned plugin is a bigger problem than one deprecation warning. Never edit a plugin’s files in place, as the next update overwrites your fix. See our WordPress PHP upgrade service if a host upgrade has left you with a list of broken plugins.

For Composer packages, the same applies: composer outdated --direct shows which ones have newer releases, which usually fix it.

Part of a bigger picture

Dynamic properties are usually the loudest symptom of code that hasn’t been touched since before PHP 8.2. With PHP 8.2 reaching end of life on 31 December 2026, many sites are now jumping straight to PHP 8.5, which brings its own deprecations on top. Fixing dynamic properties properly, rather than silencing them, is one of the cheapest ways to make that jump and the eventual PHP 9 upgrade uneventful. Check the latest PHP version and the PHP end-of-life dates to see where your version stands.

Frequently asked questions

Is “Creation of dynamic property is deprecated” an error?

Not yet. Since PHP 8.2 it is a deprecation notice: the code still runs and the property is still created. It is planned to become an Error exception in PHP 9, at which point the same line will stop the request.

Can I just hide the deprecation warning?

You can exclude E_DEPRECATED from error_reporting, and you should not show any errors to visitors in production anyway. But hiding the notice does not fix anything: the code will break when PHP 9 turns it into an Error, and a silenced log also hides the typos and real bugs that this deprecation is good at catching.

Does this affect Laravel models or stdClass?

No. stdClass is exempt, and so is any class with __get() and __set() magic methods, which includes Laravel Eloquent models. The warning comes from ordinary classes that assign to a property they never declared.

Is #[\AllowDynamicProperties] a proper fix?

It is a valid, supported attribute and the right choice for a class that genuinely needs arbitrary properties. As a blanket fix across a codebase it just postpones the work. Declaring the properties is better for almost every class, because it also gives you types, autocompletion and static analysis.

Why does a WordPress plugin show this warning?

Many plugins and themes were written long before PHP 8.2 and set undeclared properties on their own classes. Update the plugin first; if the warning is still there, the plugin may be unmaintained, which is worth knowing before PHP 9 or your host’s next upgrade.