最有效方式是用private或protected声明属性并配显式getter/setter;public属性绕过校验导致非法赋值、状态不一致及测试困难,php 7.4+应强制类型+private组合,setter须做类型、业务规则与一致性校验。

直接用 private 或 protected 声明属性,再配以显式定义的 getter 和 setter 方法 —— 这是 PHP 封装中限制属性被随便修改最有效、也最可控的方式。别依赖默认行为,也别把属性设成 public 然后靠文档“约定俗成”。
为什么不能用 public 属性直接暴露?
PHP 的 public 属性一旦声明,外部代码可无条件读写,绕过任何校验逻辑。哪怕你写了 setAge(),别人仍可直接执行 $user->age = -5;,而这个赋值不会触发任何检查。
常见错误现象包括:
- 数据库写入非法值(如负年龄、超长用户名)
- 对象状态不一致(比如
$status被设为未定义字符串,后续逻辑崩掉) - 单元测试难以 mock 行为(因为属性可被任意篡改,无法模拟受控状态)
PHP 7.4+ 强制类型声明 + private 是硬性组合:类型只管输入输出,可见性才管访问路径。
private vs protected:子类是否需要继承该属性?
选 private 还是 protected 不是凭感觉,而是看子类是否**必须复用或覆盖该字段的语义逻辑**。
使用场景判断:
-
private:纯内部状态,子类不该碰。例如private string $passwordHash;,子类连读都不该允许 -
protected:子类需参与控制流程。例如protected int $retryLimit = 3;,子类可重写getRetryLimit()或在构造中调整 - 永远不要用
public属性替代配置参数 —— 那该用public const或依赖注入
注意:protected 并不意味着“子类可以随便改”,它只是开放了访问权限;真正约束仍要靠子类自己重写 setter 或在父类中用 final 修饰方法。
setter 中必须做哪些校验?
一个合格的 setter 不只是赋值,它得回答三个问题:这个值合不合类型?合不合业务规则?改了会不会破坏一致性?
实操建议:
- 类型校验交给 PHP 自动完成(
function setAge(int $age): void),但别省略边界检查 - 业务规则必须手动写,比如
if ($age 150) { throw new InvalidArgumentException(...); } - 关联属性需同步处理,例如设置
$email时,自动更新$emailHash或清空缓存 - 避免在
setter中做耗时操作(如 DB 查询、HTTP 请求)—— 那属于领域服务职责,不是属性封装的事
构造函数里也得调用 setter,否则初始化绕过校验,等于白设。
有人用 __set() 和 __get() 代替显式方法?
不推荐。魔术方法会掩盖真实接口,IDE 无法提示、静态分析工具难追踪、调试时断点难打。
典型陷阱:
-
__set('age', -1)不报错,但后续getAge()可能返回非法值 - 无法对特定属性做差异化处理(比如只对
name做 trim,对id做只读拦截) - 一旦启用
__set,所有未声明属性都会被接管,容易掩盖拼写错误(如$user->nmae = 'foo';不报错)
除非你在写 ORM 映射层或 DTO 工具类,否则老老实实写 setName() 和 getName()。
最容易被忽略的一点:封装不是加个 private 就完事,而是整套访问路径的收口。哪怕属性是 private,如果 getter 返回的是可变引用(比如数组或对象),外部仍可能间接修改内部状态 —— 这时候得考虑返回深拷贝或只读代理。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











