PHP 终结了动态属性。原因在此(以及该怎么办)
发布: (2025年12月18日 GMT+8 01:44)
4 min read
原文: Dev.to
Source: Dev.to
抱歉,我需要您提供要翻译的具体文本内容。请粘贴文章的正文(除代码块和 URL 之外),我将按照要求将其翻译成简体中文并保留原有的格式。
什么是动态属性?
动态属性是指在对象上添加的、未在其类中声明的属性。
class User {}
$user = new User();
$user->age = 30; // ⚠️ Dynamic property
问题
静默拼写错误 → 隐蔽的 Bug
$user->custmer_email = 'john@example.com'; // Typo!
echo $user->customer_email; // null 😱
缺乏类型安全
$user->age = 30; // int
$user->age = 'thirty'; // string – no error
$user->age = null; // also "fine"
IDE 与工具限制
- 没有自动补全
- 没有重构支持
- 静态分析工具无法检测问题
架构衰退
// Anemic domain model
$user->cached_data = $something;
$user->temp_flag = true;
$user->random_stuff = $whatever;
修复方法:显式声明属性
现代 PHP 写法
class User {
public string $name;
public int $age;
public bool $isActive;
}
构造函数属性提升(PHP 8+)
class CreateOrderRequest {
public function __construct(
public readonly string $customerId,
public readonly array $items,
public readonly ?string $promoCode = null,
) {}
}
Laravel‑特定解决方案
❌ 不要直接添加未定义的属性
$user = User::find(1);
$user->is_verified = true; // Undefined property!
✅ 定义可填充属性和类型转换
class User extends Model {
protected $fillable = [
'name',
'email',
'is_verified',
];
protected $casts = [
'is_verified' => 'boolean',
'email_verified_at'=> 'datetime',
];
}
✅ 使用访问器
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}",
);
}
}
逃生口(请谨慎使用!)
#[\AllowDynamicProperties]
class LegacyCode {
// Opt into old behavior
}
仅在以下情况下使用:
- 旧版供应商代码
- 渐进式迁移(并有计划移除)
切勿在新代码中使用。
迁移策略
-
Run Static Analysis
./vendor/bin/phpstan analyze --level 8 ./vendor/bin/psalm --no-cache -
Fix Systematically
- Critical – 领域模型(立即修复)
- High – 控制器 / 服务(下一个冲刺)
- Medium – DTO(增量)
- Low – 测试工具(如有需要添加属性)
-
Prevent Regression
将静态分析添加到 CI:
# .github/workflows/ci.yml - name: Static Analysis run: ./vendor/bin/phpstan analyze
版本状态
| PHP 版本 | 状态 |
|---|---|
| 8.1 | ✅ 允许 |
| 8.2 | ⚠️ 已弃用(警告) |
| 9.0 | ❌ 致命错误(已移除) |
为什么此更改重要
现代 PHP 正在朝着:
- 明确优于隐式
- 默认类型安全
- 更好的工具支持
- 便于重构的代码
动态属性与这些目标冲突。
实际示例:前后对比
前 ❌
class ApiResponse {}
$response = new ApiResponse();
$response->data = $data;
$response->status = 200;
后 ✅
class ApiResponse {
public function __construct(
public readonly mixed $data,
public readonly int $status,
public readonly int $timestamp = time(),
) {}
}
$response = new ApiResponse($data, 200);
好处: 类型安全、自解释、不可变且易于重构。
关键要点
- 明确声明所有属性。
- 使用类型属性以确保安全。
- 利用 PHP 8+ 特性(构造函数属性提升、
readonly)。 - 定期运行静态分析。
- 为遗留代码规划迁移路径。
欲阅读完整指南——包括历史背景、详细迁移策略、高级 Laravel 模式等——请阅读 Medium 上的原文:
The Death of Dynamic Properties in PHP – A Deep Dive into Modern Object Design