← Back to list

The Death of Dynamic Properties in PHP: A Deep Dive into Modern Object Design

PHP Dynamic Properties: Deprecated in 8.2, Removed in 9.0 — A Complete Guide

MasteryOfLaravel · 2025-12-15 05:48 · 18 claps · 7.6 min read
#php-8 #type-safety #laravel #php #web-development
Open on Medium ↗
Wiki topics: SAF · Safety & Alignment 🌐 · Web Development 💭 · Philosophy of Spirit

The Death of Dynamic Properties in PHP: A Deep Dive into Modern Object Design

The Death of Dynamic Properties in PHP: A Deep Dive into Modern Object Design

The Death of Dynamic Properties in PHP: A Deep Dive into Modern Object Design

For years, PHP’s permissive approach to object properties was both a convenience and a curse. You could attach properties to objects on the fly, without declaring them in your class definition. This flexibility felt liberating — until it became a maintenance nightmare.

With PHP 8.2, the language community made a decisive move: dynamic properties were officially deprecated. Come PHP 9.0, they’ll be gone entirely. This isn’t just another breaking change to grumble about — it’s a fundamental shift in how we think about object-oriented design in PHP.

If you’re building Laravel applications, working with legacy codebases, or simply trying to write better PHP, understanding this change is crucial. This article explores not just the what and when, but the deeper why — and what it means for the future of PHP development.

The Phantom Properties Problem

Let’s start with a simple example that illustrates why dynamic properties are problematic:

class Order {
    public string $id;
    public float $total;
}

$order = new Order();
$order->id = 'ORD-12345';
$order->total = 99.99;
$order->custmer_email = 'john@example.com'; // Typo!

Did you catch the typo? custmer_email instead of customer_email. In traditional PHP, this code runs without complaint. The property gets created dynamically, silently storing data in the wrong place. Later, when you try to access $order->customer_email, it's null—and you're left debugging phantom data issues.

This is the core problem: dynamic properties allow invisible errors to propagate through your codebase.

A Brief History of PHP’s Permissiveness

To understand why dynamic properties existed in the first place, we need context. Early PHP (versions 4 and 5) prioritized rapid development over strict correctness. The language was designed for building quick web applications, not large-scale enterprise systems.

Dynamic properties fit this philosophy perfectly. Need a temporary value? Just slap it on the object. No ceremony, no boilerplate. For small scripts, this worked fine.

But as PHP matured and applications grew more complex, this flexibility became a liability. The same feature that made prototyping fast made maintenance painful. Teams struggled with:

  • Unclear data contracts: What properties does this object actually have?
  • Invisible state mutations: Where did this property come from?
  • Refactoring paralysis: Will changing this class name break code that dynamically adds properties?

Modern PHP is moving away from this “move fast and break things” mentality toward predictability, type safety, and developer tooling. Dynamic properties no longer fit this vision.

The Four Horsemen of Dynamic Property Problems

1. Type Safety Evaporation

Consider this seemingly innocent code:

class UserProfile {
    // No properties declared
}

$profile = new UserProfile();
$profile->age = 30;           // Looks fine
$profile->age = 'thirty';     // Also "fine"
$profile->age = null;         // Still "fine"
$profile->age = new stdClass(); // PHP shrugs

Without declared properties, PHP has no type information to enforce. Your age field can morph from integer to string to object without warning. Type hints on method parameters can't save you here—by the time you pass $profile->age to a typed method, the damage is done.

This creates a trust problem in your codebase. You can’t rely on objects maintaining their invariants.

2. Tooling Blindness

Modern PHP development relies heavily on IDE intelligence and static analysis tools like PHPStan and Psalm. These tools work by analyzing your code structure — what properties exist, what types they have, what methods are available.

Dynamic properties are invisible to these tools:

$user->firstName = 'John'; // No autocomplete
$user->firstNme;           // No error warning about typo

Your IDE can’t help you write correct code, and your static analyzer can’t catch bugs before they reach production. You’re coding blind.

3. The Typo Time Bomb

This is perhaps the most insidious issue. A simple typo creates a working program that silently does the wrong thing:

// Intended code
$invoice->paymentStatus = 'paid';

// Actual code (typo in property name)
$invoice->paymnetStatus = 'paid';

// Later...
if ($invoice->paymentStatus === 'paid') { // null !== 'paid'
    sendReceipt($invoice);
}

The receipt never sends. The customer complains. You spend an hour debugging. The typo was invisible because PHP happily created a new property with the misspelled name.

In large Laravel applications with dozens of models and hundreds of properties, these bugs are devastatingly difficult to track down.

4. Architectural Decay

Dynamic properties encourage poor object-oriented design:

// Anemic domain model
class User {
    public string $name;
}

// Later, somewhere else in the codebase
$user = User::find($id);
$user->cached_permissions = $this->calculatePermissions($user);
$user->last_accessed = now();
$user->temporary_flag = true;

This violates several OOP principles:

  • Single Responsibility: The User object is being used as a dumping ground for unrelated data
  • Encapsulation: Internal state is exposed and modified arbitrarily
  • Intention-Revealing Names: The class definition doesn’t reflect its actual usage

Over time, objects become bags of properties with no clear boundary or purpose. Your domain model becomes anemic, and business logic scatters across the codebase.

The Timeline of Change

This gives the community a migration window. PHP 8.2 acts as a transition period where you can identify problem areas without breaking existing code. By PHP 9.0, the training wheels come off.

For Laravel developers, this timeline is particularly important. Laravel 11 targets PHP 8.2+, meaning deprecation warnings are now active. If you’re seeing these warnings, it’s time to act — before PHP 9.0 makes them errors.

The Right Way Forward: Explicit Property Declaration

The modern PHP approach is simple: declare what you mean.

Basic Declaration

class Product {
    public string $name;
    public float $price;
    public bool $inStock;
}

This tells PHP, your IDE, and future maintainers: “A Product has exactly these three properties, with these exact types.”

Constructor Property Promotion (PHP 8.0+)

For DTOs and value objects, constructor property promotion is elegant:

class CreateOrderRequest {
    public function __construct(
        public readonly string $customerId,
        public readonly array $items,
        public readonly ?string $promoCode = null,
    ) {}
}

This combines declaration, initialization, and immutability in a concise syntax. It’s particularly useful for request objects, events, and commands.

Typed Properties with Defaults

class ShoppingCart {
    public array $items = [];
    public float $subtotal = 0.0;
    public ?string $couponCode = null;
}

Defaults make objects safe to instantiate without constructors, while maintaining type safety.

Laravel-Specific Patterns

Laravel developers face unique challenges with this change because Eloquent models historically relied on some dynamic behavior.

Common Laravel Anti-Pattern

// ❌ Don't do this
$user = User::find(1);
$user->is_verified = true;  // Undefined property
$user->verification_token = Str::random(32);
$user->save();

If these properties aren’t in your database schema or declared in the model, you’re creating dynamic properties.

The Laravel-Correct Approach

Option 1: Database Columns + Casts

class User extends Model {
    protected $fillable = [
        'name',
        'email',
        'is_verified',
        'verification_token',
    ];

    protected $casts = [
        'is_verified' => 'boolean',
        'email_verified_at' => 'datetime',
    ];
}

Option 2: Accessor for Computed Properties

class User extends Model {
    protected $appends = ['full_name'];

    public function getFullNameAttribute(): string {
        return "{$this->first_name} {$this->last_name}";
    }
}

Option 3: Custom Attributes (PHP 8+)

use Illuminate\Database\Eloquent\Casts\Attribute;

class User extends Model {
    protected function fullName(): Attribute {
        return Attribute::make(
            get: fn() => "{$this->first_name} {$this->last_name}",
        );
    }
}

All three approaches maintain type safety while working with Eloquent’s magic.

The AllowDynamicProperties Escape Hatch

PHP 8.2 introduced the #[AllowDynamicProperties] attribute as a temporary bridge:

#[\AllowDynamicProperties]
class LegacyDataTransferObject {
    // Opt into old behavior
}

When to Use It

This attribute should be rare and intentional. Acceptable use cases:

  1. Legacy vendor code you cannot modify
  2. Gradual migration of large codebases (with a plan to remove it)
  3. Interop with dynamic systems (rare cases like testing frameworks)

When NOT to Use It

❌ New code ❌ Business logic objects ❌ Domain models ❌ As a permanent solution

Think of #[AllowDynamicProperties] as a "code smell suppressor"—it masks the problem rather than solving it. Use it sparingly, document why, and plan its removal.

Migration Strategies for Large Codebases

If you’re maintaining a legacy Laravel application, the path forward requires strategy.

Step 1: Detect Dynamic Properties

Use static analysis:

# PHPStan
./vendor/bin/phpstan analyze --level 8

# Psalm
./vendor/bin/psalm --no-cache

Both tools will flag dynamic property usage in PHP 8.2+.

Step 2: Categorize Issues

Create a migration plan based on severity:

  • Critical: Core domain models (fix immediately)
  • High: Controllers and services (fix in next sprint)
  • Medium: DTOs and value objects (refactor incrementally)
  • Low: Test utilities (add attribute if necessary)

Step 3: Refactor Systematically

For each class:

  1. List all property assignments in the codebase
  2. Determine intended types
  3. Add property declarations
  4. Run tests
  5. Remove #[AllowDynamicProperties] if present

Step 4: Prevent Regression

Add to your CI pipeline:

# .github/workflows/ci.yml
- name: Static Analysis
  run: ./vendor/bin/phpstan analyze --error-format=github

This prevents new dynamic properties from entering the codebase.

Real-World Case Study: Refactoring a DTO

Before (Dynamic Properties)

class ApiResponse {
    // Nothing declared
}

function buildResponse($data, $status) {
    $response = new ApiResponse();
    $response->data = $data;
    $response->status = $status;
    $response->timestamp = time();
    return $response;
}

Problems:

  • No type safety
  • Unclear structure
  • Easy to introduce typos
  • Hard to refactor

After (Explicit Declaration)

class ApiResponse {
    public function __construct(
        public readonly mixed $data,
        public readonly int $status,
        public readonly int $timestamp = time(),
    ) {}
}

function buildResponse($data, $status): ApiResponse {
    return new ApiResponse($data, $status);
}

Benefits:

  • Type-safe
  • Self-documenting
  • Immutable by default
  • Refactor-friendly

The refactored version is clearer, safer, and more maintainable.

Beyond Dynamic Properties: The Bigger Picture

This deprecation is part of a broader evolution in PHP’s design philosophy:

PHP’s Modern Design Principles

  1. Explicit over implicit: Declare intentions clearly
  2. Type safety by default: Use type hints everywhere
  3. Tooling-first: Code should be analyzable by machines
  4. Immutability when possible: Reduce accidental state changes

These principles are reflected in recent PHP features:

  • Typed properties (PHP 7.4)
  • Union types (PHP 8.0)
  • Constructor property promotion (PHP 8.0)
  • Readonly properties (PHP 8.1)
  • Enums (PHP 8.1)

Dynamic properties don’t align with any of these principles. Their removal is inevitable and necessary.

Preparing for PHP 9.0

PHP 9.0 will throw a fatal error for dynamic properties (unless #[AllowDynamicProperties] is present). Here's how to prepare:

Immediate Actions

  1. Upgrade to PHP 8.2 if possible to surface warnings
  2. Enable strict error reporting in development
  3. Run static analysis regularly
  4. Audit your models for undeclared properties

Long-Term Strategy

  1. Establish coding standards that forbid dynamic properties
  2. Train your team on modern PHP patterns
  3. Refactor incrementally rather than in one big-bang migration
  4. Document architectural decisions about object design

Final Thoughts: Embracing Constraint

The removal of dynamic properties might feel restrictive at first. But constraints often lead to better design.

By forcing developers to think explicitly about object structure, PHP 8.2+ encourages:

  • Clearer domain models that reflect business reality
  • Stronger type safety that catches bugs at compile time
  • Better tooling support that boosts productivity
  • Easier refactoring that reduces fear of change

This change aligns PHP with other modern languages like TypeScript, Rust, and Swift — languages that prioritize developer experience through strong type systems.

For Laravel developers specifically, this is an opportunity to revisit your models, clean up technical debt, and embrace modern PHP patterns. Your codebase will be more maintainable, your bugs will be easier to find, and your team will be more productive.

The death of dynamic properties isn’t the end of something — it’s the beginning of better PHP.

Hi ,I write about Laravel & PHP best practices.

If you found this helpful:

— Follow me for more Laravel deep-dives

— Subscribe to get my articles in your inbox Got questions?

Drop them in the comments below! 👇


메타데이터
post_id
d26b43c8ffa0
slug
the-death-of-dynamic-properties-in-php-a-deep-dive-into-modern-object-design-d26b43c8ffa0
url
https://medium.com/@masteryoflaravel/the-death-of-dynamic-properties-in-php-a-deep-dive-into-modern-object-design-d26b43c8ffa0
canonical_url
https://medium.com/@masteryoflaravel/the-death-of-dynamic-properties-in-php-a-deep-dive-into-modern-object-design-d26b43c8ffa0
author_url
https://medium.com/@masteryoflaravel
status
ok
fetched_at
2026-06-28 04:42:08