应将业务相关属性声明为protected而非private,以支持子类定制;构造函数中调用protected init()方法拆分构建流程;通过__get/__set和事件钩子实现动态行为注入;优先定义接口契约并封装销毁逻辑。

用 protected 替代 private 声明属性
直接暴露 private 属性会锁死子类对状态的访问路径,后续想加钩子、日志或兼容逻辑就只能重写整个方法。改用 protected 是最轻量的预留方式——既防止外部随意修改,又允许继承类在关键节点介入。
常见错误现象:父类用 private $config,子类想覆盖部分配置项却只能复制构造逻辑,导致维护成本翻倍。
- 所有业务相关属性,只要未来可能被定制,一律声明为
protected - 配合 getter/setter 提供受控访问,但不要把 setter 做成空壳(比如只校验不触发事件)
- 若必须用
private(如敏感凭证),则额外提供protected function initXxx()钩子供子类重写初始化行为
在 __construct 里调用 protected 初始化方法
把对象构建过程拆成两段:基础赋值由父类完成,可扩展逻辑交给 protected 方法。这样子类只需重写那个方法,不用重复处理参数校验或依赖注入。
使用场景:数据库连接类需要支持不同驱动,但连接字符串解析逻辑通用;用户类要兼容 LDAP 和本地密码策略,但邮箱验证规则一致。
立即学习“PHP免费学习笔记(深入)”;
- 父类
__construct()中最后一行调用$this->init(); -
init()默认为空,子类按需重写,不强制调用 parent::init() - 避免在
init()里做耗时操作(如网络请求),否则无法被单元测试 mock
预留魔术方法和事件钩子
PHP 的 __get/__set 不仅能实现懒加载,更是动态行为注入的入口。配合内部事件系统(如 $this->trigger('before_save')),比硬编码回调更灵活。
性能影响:__get 被频繁调用时会有微小开销,但比反射或动态方法调用低一个数量级;事件触发本身可设开关,默认关闭。
- 在关键生命周期点(如
save()、toArray())前后插入trigger()调用 - 事件名用字符串而非常量,方便子类覆盖(如
'user.before_create') - 不要在
__get里直接 new 对象,改用工厂方法或容器获取,否则破坏解耦
接口定义比具体实现更重要
很多“预留扩展”失败,是因为一开始就把类设计成封闭结构。真正可扩展的封装,核心是定义好契约:哪些方法必须存在、哪些参数可选、哪些返回值可被修饰。
容易踩的坑:用抽象类强求子类实现一堆无意义方法;或过度依赖 trait 导致调用链混乱。
- 优先定义
interface(如UserValidatorInterface),再让具体类去实现 - 构造函数参数尽量用接口类型(
__construct(EmailValidatorInterface $validator)) - 返回值类型声明用联合类型(
string|int|null)而非具体类,给子类留出替换空间
__destruct() 无法被继承,资源清理逻辑一旦写死就很难替换。如果类持有连接、临时文件或锁,务必通过 protected function cleanup(): void 封装,并在 __destruct() 中调用它。



















