PHP Killed Dynamic Properties. Here's Why (And What to Do)
Source: Dev.to
What Are Dynamic Properties?
Dynamic properties are properties added to an object without being declared in its class.
class User {}
$user = new User();
$user->age = 30; // ⚠️ Dynamic property
The Problem
Silent Typos → Hidden Bugs
$user->custmer_email = 'john@example.com'; // Typo!
echo $user->customer_email; // null 😱
No Type Safety
$user->age = 30; // int
$user->age = 'thirty'; // string – no error
$user->age = null; // also "fine"
IDE & Tooling Limitations
- No autocomplete
- No refactoring support
- Static analysis tools cannot detect issues
Architecture Decay
// Anemic domain model
$user->cached_data = $something;
$user->temp_flag = true;
$user->random_stuff = $whatever;
The Fix: Declare Properties Explicitly
Modern PHP Way
class User {
public string $name;
public int $age;
public bool $isActive;
}
Constructor Property Promotion (PHP 8+)
class CreateOrderRequest {
public function __construct(
public readonly string $customerId,
public readonly array $items,
public readonly ?string $promoCode = null,
) {}
}
Laravel‑Specific Solutions
❌ Don’t Add Undefined Properties Directly
$user = User::find(1);
$user->is_verified = true; // Undefined property!
✅ Define Fillable Attributes and Casts
class User extends Model {
protected $fillable = [
'name',
'email',
'is_verified',
];
protected $casts = [
'is_verified' => 'boolean',
'email_verified_at'=> 'datetime',
];
}
✅ Use Accessors
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}",
);
}
}
The Escape Hatch (Use Sparingly!)
#[\AllowDynamicProperties]
class LegacyCode {
// Opt into old behavior
}
Use this only for:
- Legacy vendor code
- Gradual migration (with a plan to remove)
Never use it for new code.
Migration Strategy
-
Run Static Analysis
./vendor/bin/phpstan analyze --level 8 ./vendor/bin/psalm --no-cache -
Fix Systematically
- Critical – Domain models (fix now)
- High – Controllers / Services (next sprint)
- Medium – DTOs (incremental)
- Low – Test utilities (add attribute if needed)
-
Prevent Regression
Add static analysis to CI:
# .github/workflows/ci.yml - name: Static Analysis run: ./vendor/bin/phpstan analyze
Version Status
| PHP version | Status |
|---|---|
| 8.1 | ✅ Allowed |
| 8.2 | ⚠️ Deprecated (warning) |
| 9.0 | ❌ Fatal error (removed) |
Why This Change Matters
Modern PHP is moving toward:
- Explicit over implicit
- Type safety by default
- Better tooling support
- Refactor‑friendly code
Dynamic properties conflict with these goals.
Real Example: Before & After
Before ❌
class ApiResponse {}
$response = new ApiResponse();
$response->data = $data;
$response->status = 200;
After ✅
class ApiResponse {
public function __construct(
public readonly mixed $data,
public readonly int $status,
public readonly int $timestamp = time(),
) {}
}
$response = new ApiResponse($data, 200);
Benefits: type‑safe, self‑documenting, immutable, and refactor‑friendly.
Key Takeaways
- Declare all properties explicitly.
- Use typed properties for safety.
- Leverage PHP 8+ features (constructor promotion,
readonly). - Run static analysis regularly.
- Plan a migration path for legacy code.
For the full guide—including historical context, detailed migration strategies, advanced Laravel patterns, and more—read the original article on Medium:
The Death of Dynamic Properties in PHP – A Deep Dive into Modern Object Design