全部用 public 属性等于放弃封装,是埋雷;应默认从 private 开始,通过 getter/setter 统一管控读写与校验,确保值始终合法。

直接说结论:全部用 public 属性等于放弃封装,等于把类的内部状态裸奔交给外部代码随意读写——这不是方便,是埋雷。
public 属性导致赋值失控,连基础类型都拦不住
PHP 在运行时才做类型检查(即使用了 PHP 7.4+ 的类型声明),但 public 属性绕过了所有控制入口:
- 构造函数、
setter方法里的校验逻辑完全失效 - 外部代码可直接
$user->age = -5;或$user->email = [];,而不会触发任何验证 - 即使你写了
private int $age;,一旦改成public int $age;,类型只在赋值时抛TypeError,但错误发生在调用方,不是在类内统一处理点
常见现象:
- API 返回数据里突然出现
"age": null,查半天发现是某处直接写了$user->age = null; - 测试通过,上线后某个第三方 SDK 直接改了你的
$order->status字符串为整数,后续逻辑全崩
建议做法:
- 所有需要被外部读写的属性,优先用
private+getter/setter -
setter中做范围检查、格式标准化、空值处理(比如把空字符串转为null) - 不要依赖“大家自觉不乱赋值”——协作人数 > 2 时这条就失效
protected 和 private 不是加锁,是划边界
很多人以为 private 是为了“防同事”,其实核心作用是 防自己未来重构时踩坑:
-
private string $passwordHash;意味着:密码哈希永远只在本类内生成、比对、重置 - 一旦哪天你要接入新密码算法,只需改
setPassword()和verifyPassword(),不用 grep 全项目找$user->passwordHash = ... -
protected适用于子类需复用的逻辑,比如基类定义protected array $filters;,子类可扩展但不暴露给外部
容易踩的坑:
- 把本该
private的配置常量写成public const,结果业务方直接引用User::MAX_LOGIN_ATTEMPTS,后来想改成动态配置就动不了 - 用
protected声明属性却没配getter,子类只能靠反射硬读,一升级 PHP 版本就报错
PHP 8.4 起支持不对称可见性,但 public 仍是最大风险面
PHP 8.4 引入了 public private(set) 这种语法,允许“可读不可写”,看起来很美:
class Order {
public function __construct(
public string $id,
public private(set) string $status,
) {}
}
但注意:
-
public本身仍开放读取权限,外部仍能$order->status直接取值,无法加缓存、日志、懒加载等逻辑 - 如果哪天你想把
$status改成从数据库实时查,就得破环性修改所有读取点 - 而用
private string $status;+public function getStatus(): string,你随时可以切换实现,调用方零感知
所以,真正安全的起点不是“怎么限制写”,而是“默认不开放读”——除非你明确需要被外部访问,否则一律从 private 开始。
关键点其实就一个:属性可见性不是关于“能不能访问”,而是关于“谁负责保证这个值始终合法”。把责任推给调用方,迟早出事。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











