Starting with PHP 8.2, dynamic properties are deprecated. In PHP 9.0, they're completely gone.
If you're seeing deprecation warnings in your Laravel 11 projects, this is why.
What Are Dynamic Properties?
Properties added to objects without being declared in the class:
class User {}
$user = new User();
$user->age = 30; // β οΈ Dynamic property
The Problem
- Silent Typos = Hidden Bugs
$user->custmer_email = 'john@example.com'; // Typo!
// Later...
echo $user->customer_email; // null π±
- No Type Safety
$user->age = 30; // int
$user->age = 'thirty'; // string - no error!
$user->age = null; // also "fine"
- IDE Can't Help You
No autocomplete
No refactoring support
Static analysis tools are blind
- 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 Do This
$user = User::find(1);
$user->is_verified = true; // Undefined property!
β
Do This Instead
class User extends Model {
protected $fillable = [
'name',
'email',
'is_verified',
];
protected $casts = [
'is_verified' => 'boolean',
'email_verified_at' => 'datetime',
];
}
β
Or 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
}
Only use this for:
Legacy vendor code
Gradual migration (with a plan to remove)
Never use for new code!
Migration Strategy
./vendor/bin/phpstan analyze --level 8
./vendor/bin/psalm --no-cache
2. Fix Systematically
Critical: Domain models (fix now)
High: Controllers/Services (next sprint)
Medium: DTOs (incremental)
Low: Test utilities (add attribute if needed)
3. Prevent Regression
Add static analysis to CI:
# .github/workflows/ci.yml
- name: Static Analysis
run: ./vendor/bin/phpstan analyze
| Version | Status |
|---|---|
| PHP 8.1 | β Allowed |
| PHP 8.2 | β οΈ Deprecated warning |
| PHP 9.0 | β Fatal error |
Why This Change Matters
Modern PHP is moving toward:
β
Explicit over implicit
β
Type safety by default
β
Better tooling support
β
Refactor-friendly code
Dynamic properties don't fit this vision.
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, 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 migration for legacy code
This is a condensed version. For the complete guide including:
Historical context of PHP's design evolution
Detailed migration strategies for large codebases
Advanced Laravel patterns
Philosophical discussion on constraints
More real-world examples
π Read the full article on Medium:
Top comments (3)
A few addendums:
The AllowDynamicProperties attributes is fine to use on new classes, if that is the best option. Most of the time the best option is defined properties. If the names are dynamic but follow a pattern the
__getand__setmethods can be used. When the dynamic names are unpredictable then go for the attribute.If you read the RFC, you will be able to use dynamic properties on a
stdClassinstance in PHP 9For the Eloquent model, adding properties is just fine because it has a
__getand_setfunction to intercept the dynamic properties.I think it is guiding developers to write better code, but at the same time not making the language too rigid.
Good points π
I agree β PHP is not trying to ban dynamic properties entirely, but rather encourage more intentional and predictable designs.
Defined properties should be the default, get/set work well when thereβs a clear pattern, and #[AllowDynamicProperties] is a reasonable escape hatch for truly unpredictable cases.
Frameworks like Eloquent already handle this safely via magic methods, so they are not really affected. Overall, this feels more like guidance toward better code than unnecessary rigidity.
Some comments may only be visible to logged-in visitors. Sign in to view all comments.