PHP类的封装与继承详解
前言封装encapsulation和继承inheritance经常被一起提但它们在解决完全不同的问题。封装回答「谁能动这份数据」继承回答「哪些类共享同一套契约」。把两者混在一起的后果很典型为了「复用代码」而随便extends最后得到一个class User extends Database这种关系上根本不成立的类层次。第二个高频误解是「protected就是给子类用的public比private更灵活所以更好」。实际上protected意味着任何子类都能直接改父类的内部状态——子类一旦多起来父类的封装就名存实亡。PHP 官方手册对可见性的表述很明确private成员只在声明它的那个类里可见子类完全看不到。本文先讲清三档可见性的真实语义再讲继承的硬性规则单继承、可见性不能收窄、构造器不自动调用最后给出封装与继承配合使用的正确姿势以及 PHP 8.1 引入的readonly如何让「不可变值对象」既封装又简洁。一、可见性封装的全部实现手段修饰符本类内部子类外部代码public可见可见可见protected可见可见不可见private可见不可见不可见var遗留写法可见可见可见等价于publicprivate的「不可见」需要理解到一个关键程度私有成员不参与继承。子类里如果写了一个与父类私有属性同名的属性那是两个互不相干的属性?php // 适用于 PHP 7.4class ParentClass{private string $name parent;public function getName(): string{return $this-name; // 始终读父类自己的 $name}}class ChildClass extends ParentClass{private string $name child; // 与父类那个没有任何关系}echo (new ChildClass())-getName(), PHP_EOL; // parent这一点在调试时会造成「明明赋值了却读不到」的诡异现象。要打破这个局面getName()必须改成读写protected属性或者提供一个protected的取值方法供子类重写。另一个容易忽略的事实var关键字到今天仍然合法但它只是public的旧写法不提供任何封装。看到var就该知道那是 PHP 4 时代的遗留代码。二、继承的硬性规则PHP 的继承有五条必须记住的规则1. 单继承。class C extends A, B是语法错误。需要多个契约就实现多个接口需要复用多份方法就用 trait。2. 只能保持或放宽可见性。父类方法是public子类不能改成protected/private父类protected不能改成private。反过来放宽是允许的。?php // 适用于 PHP 5.0class P{public function a(): void {}protected function b(): void {}}class C extends P{public function b(): void {} // 合法protected 放宽成 public// protected function a(): void {} // 致命错误不能收窄 public}3. 静态与非静态不能互相重写。把父类的静态方法改成实例方法或反过来都会报Cannot make static method ... non static这类致命错误。4. 构造器是普通方法遵循同样的继承规则。子类没有定义自己的构造器时父类构造器会被继承并自动执行子类定义了构造器时父类的构造器不会自动执行需要手动parent::__construct()。这也是「子类里的初始化代码没跑」这类问题的根源。5.final用于封闭。final class不能被继承final public function不能在子类被重写。final与abstract不能同时修饰一个类那样 PHP 会直接报致命错误。三、封装与继承配合的正确姿势既然protected会把内部状态暴露给所有子类那正确的分工应该是父类的内部状态用private只通过受控方法暴露必要的读写需要子类参与的扩展点用protected方法并且父类只调用这个方法的契约不关心实现不允许子类改动的流程步骤用final锁死。下面这段代码完整演示了这个模式Template Method模板方法可以直接存成repo.php执行。?php // 适用于 PHP 8.1declare(strict_types1);abstract class Repository{/** var arrayint, arraystring, mixed */private array $rows [];// 对外接口是 final 的子类无法绕过校验直接写入final public function save(array $row): int{$this-validate($row);$id $this-nextId();$this-rows[$id] $row;return $id;}final public function find(int $id): ?array{return $this-rows[$id] ?? null;}// 扩展点子类必须实现自己的校验规则abstract protected function validate(array $row): void;// 扩展点子类可以重写生成主键的方式protected function nextId(): int{return $this-rows [] ? 1 : max(array_keys($this-rows)) 1;}}final class UserRepository extends Repository{protected function validate(array $row): void{if (!isset($row[name]) || $row[name] ) {throw new InvalidArgumentException(name 不能为空);}}}$repo new UserRepository();$id $repo-save([name alice]);echo $id, PHP_EOL; // 1var_dump($repo-find($id)[name]); // string(5) alice注意$rows是private子类UserRepository拿不到它也就没法绕过validate()直接写数据。子类能做的只有实现validate()和重写nextId()这两处正是父类留出的扩展点。self与static在这里也有讲究。父类里如果用self::调用一个被子类重写过的方法走的是父类的实现编译期绑定要跟随实际的子类实现得用$this-实例方法或static::后期静态绑定PHP 5.3。四、PHP 8.1 的readonly不可变值对象readonly属性解决了「封装好了但对象仍可被内部改来改去」的问题它只能被初始化一次且必须声明类型。?php // 适用于 PHP 8.1declare(strict_types1);class Money{public function __construct(public readonly int $amount,public readonly string $currency,) {if ($amount 0) {throw new InvalidArgumentException(金额不能为负);}}public function add(Money $other): self{if ($this-currency ! $other-currency) {throw new InvalidArgumentException(币种不一致);}return new self($this-amount $other-amount, $this-currency);}}$total (new Money(100, CNY))-add(new Money(50, CNY));echo $total-amount, PHP_EOL; // 150「改不了」意味着所有修改都要造一个新对象比如上面的add()这正是值对象Value Object的行为方式没有共享可变状态就不会出现「谁把它改了」的排查难题。PHP 8.2 进一步引入了只读类readonly class把它写在一个类上等于给这个类的所有属性都加readonly。PHP 7.4 上等价的写法是把属性声明为private只提供 getter并去掉构造器属性提升?php // 适用于 PHP 7.4class Money7{private int $amount;private string $currency;public function __construct(int $amount, string $currency){if ($amount 0) {throw new InvalidArgumentException(金额不能为负);}$this-amount $amount;$this-currency $currency;}public function amount(): int { return $this-amount; }public function currency(): string { return $this-currency; }}常见坑点❌ 为了复用几个方法就extends比如让User extends Database—— ✅ 继承应当表达 is-a关系不成立时改用组合把依赖对象通过构造器注入进来。❌ 子类把父类方法从public收窄为protected或private—— ✅ 致命错误重写只能保持或放宽可见性。❌ 子类定义了构造器却忘了parent::__construct()—— ✅ 只要子类有自己的构造器父类构造器就不会自动执行需要时必须显式调用。❌ 对没有构造器的父类写parent::__construct()—— ✅ 会报Cannot call constructor调用前先确认父类确实声明了构造器。❌ 以为protected比private更「安全地灵活」 —— ✅protected会把内部状态交给所有子类内部数据优先private只把真正的扩展点设为protected。❌ 属性声明为public同时又写一对不做任何事的 getter/setter —— ✅ 两者自相矛盾封装就老老实实private 受控方法。❌ 试图修改readonly属性如$m-amount 10;或$m-amount 1;—— ✅ 只读属性只能在声明它的类作用域内初始化一次要改就造新对象。❌ 在父类里用self::调用希望被子类重写的方法 —— ✅self::是编译期绑定走父类实现需要多态用$this-静态上下文用static::。总结主题规则常见错误private仅本类可见不参与继承以为子类能「重写」父类私有属性protected本类与子类可见用得太多把内部状态全暴露给子类public/var所有作用域可见用var以为是老式封装继承单继承可见性不可收窄静态/非静态不可互换为复用代码而继承构造器子类有自己的构造器时不自动调用父类忘记parent::__construct()final封闭类或方法与abstract同时使用致命错误readonlyPHP 8.1初始化一次必须带类型想「更新」只读属性结论封装的抓手是可见性继承的边界是「能不能替换父类」——判断标准是里氏替换子类对象放到任何期待父类的地方都必须正常。实践中父类的内部状态用private守住把扩展点收窄成少数几个protected抽象方法再用final锁住不该变的主流程值对象则一律用readonlyPHP 8.1做成不可变这样「谁改了它」这类问题就从根上消失了。

相关新闻

免杀与检测的对抗:从静态特征到行为分析的检测链路拆解

免杀与检测的对抗:从静态特征到行为分析的检测链路拆解

“免杀”这两个字在安全圈子里属于典型的“人人都聊、没几个聊透”的话题。我这两年的工作重心是跟恶意样本和检测系统打交道,从最开始只会拿杀毒软件扫一扫,到后来天天对着 EDR 控制台看拦截日志,再到现在自己写检测规则跟红队样本互相试探&…

2026/10/10 12:43:26 阅读更多 →
逻辑漏洞挖掘实战:从越权到竞态条件,比SQL注入更难防

逻辑漏洞挖掘实战:从越权到竞态条件,比SQL注入更难防

1. 逻辑漏洞到底是什么,为什么它比SQL注入还难防做了这么多年安全测试,我有个特别深的感触:很多团队把WAF、RASP、漏洞扫描器堆了一整套,SQL注入、XSS这种常规问题倒是堵得死死的,结果业务上线没几天,就被一…

2026/10/10 12:43:26 阅读更多 →
Notepad-- 跨平台文本编辑器:安装配置与批量替换、编码转换实操指南

Notepad-- 跨平台文本编辑器:安装配置与批量替换、编码转换实操指南

1. 为什么我劝你先搞清楚 Notepad-- 到底解决什么问题很多人第一次听到 Notepad-- 这个名字,第一反应是"这不就是个山寨版 Notepad 吗"。我一开始也这么想,直到有次需要在几台不同系统的机器上处理一批日志文件,Windows 上顺手用的…

2026/10/10 12:43:26 阅读更多 →

最新新闻

Codex插件生态实战指南:12个必备开发提效工具

Codex插件生态实战指南:12个必备开发提效工具

1. Codex 是什么,以及为什么它需要插件生态支撑Codex 不是某个具体软件的官方名称,而是开发者社区中对一类基于大语言模型(LLM)能力构建的代码辅助工具的泛称。它常被用来指代那些深度集成在 IDE 中、能理解上下文、自动生成函数、…

2026/10/10 14:10:52 阅读更多 →
GraphQL .NET 官方文档站构建与发布指南:基于 Gatsby 的 docs2 站点全解析

GraphQL .NET 官方文档站构建与发布指南:基于 Gatsby 的 docs2 站点全解析

后端 【免费下载链接】graphql-dotnet GraphQL for .NET 项目地址: https://gitcode.com/gh_mirrors/gr/graphql-dotnet 点击查看 免费下载 本文以 graphql-dotnet 仓库中 docs2/README.md 为骨架,系统讲解 GraphQL .NET 官方文档站(docs2&a…

2026/10/10 14:10:52 阅读更多 →
iOS免签分发实战:Web Clip封装、nginx配置与绿标证书避坑指南

iOS免签分发实战:Web Clip封装、nginx配置与绿标证书避坑指南

简介:一套聚焦iOS 14系统的苹果免签分发技术资料包,面向需要绕过App Store审核、实现快速分发或内部测试的iOS开发者与企业。核心围绕Web Clip与HTML5 Web App封装方案,帮助实现应用绿标安全标识、隐藏顶部网址、阻断非预期URL跳转等目标。资…

2026/10/10 14:10:52 阅读更多 →
单通道200Gbps:PAM4、SerDes与信号完整性的工程极限挑战

单通道200Gbps:PAM4、SerDes与信号完整性的工程极限挑战

第一次看到200Gbps这个目标时,我的第一反应是:单条信道跑到200G,这不是在挑战协议,这是在挑战物理定律。前几年我们还在112Gbps的SerDes上反复调整均衡参数、和收发端的预加重较劲,转眼间产业界已经把目光锁死在单通道…

2026/10/10 14:10:52 阅读更多 →
研发项目管理制度落地指南:从流程标准化到量化红线

研发项目管理制度落地指南:从流程标准化到量化红线

简介:研发项目管理制度.doc 是一份面向研发管理者、项目经理及团队成员的制度范本,系统梳理了研发项目从规划、实施、监控到收尾的全生命周期管理规范,适用于需要建立或优化研发管理流程的企业与项目团队。压缩包内仅含1个doc文档&#xff0c…

2026/10/10 14:10:52 阅读更多 →
SpringBoot+Vue项目申报系统开发实战:从流程设计到部署上线

SpringBoot+Vue项目申报系统开发实战:从流程设计到部署上线

搞这个项目申报系统,我其实是被身边的实际需求逼出来的。当时单位里还在用Excel收申报书,几百份文件靠邮件来回传,命名格式五花八门,审核意见散落在聊天记录里,年底归档更是灾难现场。所以当我看到“基于SpringBootVue…

2026/10/10 14:09:51 阅读更多 →

日新闻

卫星轨道分类全解析:从LEO到GEO的选型逻辑与工程实践

卫星轨道分类全解析:从LEO到GEO的选型逻辑与工程实践

1. 从“卫星轨道分类”这个标题说起:为什么值得花时间搞懂第一次接触“卫星轨道分类”这个概念,很多人会觉得它离自己很远——不就是天上的星星怎么转吗?但如果你正在做航天任务规划、遥感数据接收、星座设计,甚至只是准备一场航天…

2026/10/10 0:00:39 阅读更多 →
Spring AOP 核心原理与实战:从概念到日志切面落地

Spring AOP 核心原理与实战:从概念到日志切面落地

1. 从一个真实痛点说起:为什么你的代码里到处都是重复逻辑刚入行那会儿,我写过一个用户管理模块,注册、登录、改密码、注销四个接口。每个接口里都塞了几乎一样的日志打印、参数校验、事务开启和提交。当时觉得没什么,能跑就行。直…

2026/10/10 0:00:40 阅读更多 →
Python招聘数据采集与分析可视化:从采集清洗到薪资技能城市可视化全链路

Python招聘数据采集与分析可视化:从采集清洗到薪资技能城市可视化全链路

简介:这是一套面向计算机相关专业学生与项目实战学习者的Python数据采集与分析可视化完整项目,以Boss直聘岗位数据为对象,适合用作毕业设计、课程设计或期末大作业。资源包共38个文件,约246KB,以13个py源码文件为核心&…

2026/10/10 0:00:40 阅读更多 →

周新闻

KT148A语音芯片外挂8002D功放的工程实践指南

KT148A语音芯片外挂8002D功放的工程实践指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/10 11:14:25 阅读更多 →
LLC谐振变换器增益公式推导:从FHA等效到完整归一化表达式

LLC谐振变换器增益公式推导:从FHA等效到完整归一化表达式

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/10 1:36:08 阅读更多 →
ARM架构深度解析:从RISC设计理念到交叉编译实战

ARM架构深度解析:从RISC设计理念到交叉编译实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/10 11:14:58 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/10 5:23:50 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/9 21:32:20 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/10 10:38:42 阅读更多 →