构造函数是确保对象“一出生就合规”的唯一关口,必须校验业务逻辑而不仅是参数类型,避免子类重写方法、用private属性+显式setter控制写入,并警惕只读类与accessors的适用边界。

构造函数里做完所有校验
对象状态不合法,往往是因为初始化时就埋了雷。PHP 不会自动阻止你传入非法值,__construct 是唯一能确保“一出生就合规”的关口。
- 别只校验参数类型,要校验业务逻辑:比如
$age不能只是int,还得在 0–150 范围内 - 避免在构造函数里调用可能被子类重写的 public 方法——子类尚未完成初始化,容易出错
- 如果校验逻辑复杂,抽成私有方法(如
private function validateEmail(string $email): void),但别让它返回布尔值再由外部判断;直接抛异常更明确
用 private 属性 + 显式 setter 控制写入入口
一旦对象创建完成,所有修改都必须经过可控路径。把属性设为 private,强制走 setXxx(),是 PHP 封装最可靠的基础手段。
-
public或protected属性等于敞开大门,IDE 和类型系统拦不住运行时误赋值 - setter 里重复校验:即使构造函数已校验,也要防住后续修改(比如
$user->setEmail('')) - 不要为了“简洁”而暴露
public属性——哪怕它带类型声明(public string $name;),也无法阻止$obj->name = null;在弱类型上下文中静默失败
PHP 8.2 只读类不是万能解药
readonly class 确实能锁死所有属性,但它只适用于“创建即固定”的场景,和“保证状态合法”不是一回事。
- 只读类不校验值本身是否合法——
new User('', -5)依然能过,只是之后改不了 - 它禁止继承扩展,意味着你无法用多态方式处理不同状态变体(比如
ActiveUser/InactiveUser) - 若业务需要后期修正状态(如审核后激活、余额充值),只读类直接不可用
Accessors(PHP 8.4+)让校验更透明但需谨慎
用 get/set 修饰符写的访问器,语法上像属性访问,实际执行逻辑,看起来很理想。但要注意边界:
-
set块里不能依赖其他属性的当前值——因为多个属性的赋值顺序不确定,$obj->a = 1; $obj->b = 2;中,b的 setter 无法安全读$this->a - getter 不该有副作用(比如触发网络请求或修改内部状态),否则
echo $obj->name;可能悄悄改变对象行为 - 目前 Accessors 不支持对
private属性使用——它要求属性本身是public,所以封装性其实比传统 setter 更弱,仅适合轻量级转换场景(如格式化、默认值填充)
真正难的不是写校验逻辑,而是识别哪些状态约束是刚性的、跨生命周期有效的。比如“邮箱必须含 @ 符号”是硬规则,“用户名长度 ≤ 20”可能随产品演进放宽——这类边界条件,最好抽离到独立验证器中,而不是散落在每个 setter 里。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











