PHP 8.2 for WordPress: Readonly Classes, DNF Types, and Dynamic Property Deprecation

PHP 8.2 introduced readonly classes, Disjunctive Normal Form (DNF) types, true/false/null as standalone types, and deprecated dynamic properties — all of which affect how modern WordPress plugins should be written.

Problem: A WordPress plugin codebase is stuck on PHP 7.4 syntax — missing readonly properties, first-class callable syntax, and DNF types — making it harder to leverage modern PHP tooling and static analysis.

Solution: Upgrade incrementally using PHP 8.2 features: declare readonly on class properties that should not change after construction, replace null-prone dynamic property access with explicit declarations, and use DNF types (int|(Countable&Traversable)) to express complex type constraints. Run PHPStan at level 6+ to catch incompatibilities before deployment.

The examples below show PHP 8.2 readonly classes as value objects, DNF type declarations for complex union intersections, and the correct way to handle deprecated dynamic properties in WordPress code.

title = 'Changed'; // Error: Cannot modify readonly property

// Clone with modification using named arguments (PHP 8.1+)
$updated = new PostData( ...(array) $post, title: 'Updated Title' );

// DNF types — combine union (|) and intersection (&) types
// Useful when a parameter accepts either a countable iterable or null
function process_items( (Countable&Iterator)|null $items ): void {
    if ( null === $items ) return;
    foreach ( $items as $item ) { /* … */ }
}

// Standalone true/false/null types
function is_published( WP_Post $post ): true|false {
    return $post->post_status === 'publish';
}
// More useful: narrow return type for a factory
function find_post( int $id ): WP_Post|false {
    return get_post( $id );
}

Handle deprecated dynamic properties — critical for WordPress plugins that use magic __set:

title = 'Hello'; // dynamic property — not declared!
    }
}

// GOOD: declare all properties explicitly
class MyWidget {
    public string $title = '';
    public function setup() {
        $this->title = 'Hello';
    }
}

// If you genuinely need dynamic properties (e.g., magic __get/__set), use the attribute:
#[\AllowDynamicProperties]
class LegacyWidget extends WP_Widget {
    // dynamic properties allowed, no deprecation warning
}

// Fibers (PHP 8.1) + readonly (8.2) for async-style plugin tasks:
$fiber = new Fiber( function(): void {
    $value = Fiber::suspend( 'first' );
    echo "Got: $value
";
} );
$fiber->start();
$fiber->resume( 'hello' );

NOTE: Run php -d error_reporting=E_ALL your-plugin.php or use PHPStan with phpVersion: 80200 to catch all PHP 8.2 deprecations before deploying.

Leave Comment

Your email address will not be published. Required fields are marked *