该用 getter/setter 而非 public 属性:需默认值、兼容旧字段名、依赖计算、预留缓存/日志扩展点;避免 __get/__set;setter 必须类型校验并抛异常;getter 应无副作用,仅允许惰性初始化或轻量转换。

PHP 中什么时候该用 getter/setter 而不是 public 属性
直接暴露 $name 这类 public 属性,看似省事,但只要后续需要加验证、类型转换、日志或兼容旧数据,就只能改调用方——这在团队协作或已上线项目里基本不可行。getter/setter 的核心价值不是“封装”,而是「预留扩展点」:哪怕现在只是简单返回或赋值,也比以后硬着头皮做 BC Break 强。
典型该上的场景包括:
- 属性可能为空,但业务上必须有默认值(比如 getCreatedAt() 返回 new DateTime('now'))
- 需要兼容历史字段名(如数据库存 user_name,但对象想用 name)
- 属性值依赖其他属性计算(如 getFullName() 拼接 $this->firstName 和 $this->lastName)
- 后续可能接入缓存、事件通知或审计日志
__get/__set 魔术方法别乱用,优先写明确命名的 getXXX/setXXX
用 __get() 和 __set() 看似能少写一堆方法,但实际带来三个硬伤:IDE 无法补全、静态分析工具(如 PHPStan)失效、调试时看不出哪个属性被意外触发。除非你在写 ORM 或 DTO 映射层这类通用基建,否则别碰。
更合理的做法是:
- 属性名用小驼峰($isActive),对应方法叫 isActive() 或 getIsActive()(布尔推荐前者,符合 PSR-12)
- 非布尔属性统一用 getXXX()/setXXX(),比如 getName()/setName(string $name)
- 不要为了“对称”给只读属性写空的 setXXX(),没意义还误导调用方
setter 必须做类型和边界校验,且拒绝静默失败
很多人写的 setName($name) 只是简单赋值,结果后期传进 null 或超长字符串,直到某次导出 CSV 报错才暴露问题。setter 是第一道防线,该 throw 就 throw。
实操建议:
- 类型声明优先用 PHP 原生类型(string, int),而非 @param string 注释
- 字符串长度、数值范围、枚举值合法性等业务规则,必须在 setter 内显式检查
- 错误用 InvalidArgumentException,别吞掉异常或返回 false
- 示例:
public function setEmail(string $email): void
{
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
throw new InvalidArgumentException('Invalid email format');
}
$this->email = strtolower($email);
}
getter 通常不该有副作用,但可加轻量级惰性初始化
如果 getItems() 每次都查一次数据库,那它就不是 getter,是 query 方法——应该叫 loadItems() 或 fetchItems()。真正的 getter 应该快、确定、无 IO。
立即学习“PHP免费学习笔记(深入)”;
可以接受的例外:
- 惰性初始化对象属性(如 $this->config 第一次访问时 new Config())
- 格式转换(如把存储的 timestamp int 转成 DateTime 对象)
- 空值兜底(return $this->title ?: 'Untitled')
- 但禁止:触发网络请求、写日志、修改其他属性、抛异常(除非原始数据本身损坏)
容易被忽略的一点:如果 getter 返回的是数组或对象,考虑是否该返回副本(array_merge([], $this->items))或只读接口(Traversable),避免外部代码意外污染内部状态。



















