📘 PHP 继承与静态机制深度解析
------ 从 extends
到 static
的完整知识体系(适合深度复习)
本文涵盖 PHP 7.4 ~ PHP 8.4 的继承规则、可见性、后期静态绑定、只读属性、内部类兼容性等核心机制,结合设计原则与实际案例,助你构建完整的 OOP 认知框架。
一、继承的本质:代码复用与契约承诺
✅ 什么是继承?
使用 extends
关键字,子类可以继承父类的 public 和 protected 成员(方法、属性、常量),实现功能复用。
scala
class Animal {
public function breathe() { echo "呼吸空气\n"; }
}
class Dog extends Animal {
public function bark() { echo "汪汪叫\n"; }
}
(new Dog())->breathe(); // ✅ 继承自 Animal
🔑 继承不是"复制粘贴",而是一种 "is-a"关系 :
Dog is an Animal
。
二、继承了什么?哪些不能继承?
成员类型 | 是否继承 | 说明 |
---|---|---|
public 成员 |
✅ 是 | 外部可访问 |
protected 成员 |
✅ 是 | 子类内部可访问 |
private 成员 |
❌ 否 | 仅父类内部可用,但可通过 parent:: 间接调用 |
🧠 重要补充:private
成员的"类级封装"
同一个类的不同实例,可以互访 private
成员!
php
class BankAccount {
private $balance = 0;
public function transfer(BankAccount $other, $amount) {
$this->balance -= $amount;
$other->balance += $amount; // ✅ 合法!类内部可访问同类实例的 private 属性
}
}
$a = new BankAccount();
$b = new BankAccount();
$b->transfer($a,1000);
✅ 原因:
private
是 类级别的封装,不是实例级别的。
三、可见性规则:只能"放宽",不能"收紧"
✅ 正确:放宽可见性(允许)
scala
class Parent {
protected function foo() {}
}
class Child extends Parent {
public function foo() {} // ✅ OK:protected → public
}
❌ 错误:收紧可见性(违反 LSP)
scala
class Parent {
public function bar() {}
}
class Child extends Parent {
protected function bar() {} // ❌ Fatal Error!
}
🔥 原因:违反 里氏替换原则(LSP)
"子类对象应能替换父类对象而不破坏程序行为。"
如果
Child
的bar()
变成protected
,外部代码调用时会失败。
✅ 特例:构造方法可以"收紧"可见性!
这是 PHP 中唯一的例外。
scala
class Singleton {
public function __construct() {} // 父类 public
}
class Restricted extends Singleton {
private function __construct() {} // ✅ 允许!用于单例模式
}
💡 用途:实现单例、工厂模式,控制对象创建。
四、只读属性(readonly
)的继承规则
❌ 不允许互相覆盖
scala
class A {
public int $prop = 1;
}
class B extends A {
public readonly int $prop = 2; // ❌ Fatal Error!
}
scala
class C {
public readonly int $prop = 1;
}
class D extends C {
public int $prop = 2; // ❌ Fatal Error!
}
✅ 正确做法
-
子类可显式声明同名
readonly
属性,也就是继承,只是把代码又写一了一遍:scalaclass E extends C { public readonly int $prop; // ✅ 允许 }
-
子类可在构造函数中为
readonly
属性赋值。
🔑 原因:
readonly
是一种"不可变"契约,不能被破坏。
五、方法重写:签名必须兼容(PHP 8.0+ 更严格)
从 PHP 8.0 开始,方法重写必须满足:
- 参数兼容(支持协变/逆变)
- 返回类型兼容
- 可见性不能收紧
- 不能删除返回类型声明
否则会触发 Fatal Error(PHP 7.x 是警告)。
六、继承内部类:返回类型兼容性(PHP 8.1+)
📌 背景
PHP 8.1 为大多数内部方法"暂定添加返回类型",你必须遵守:
php
class MyData implements JsonSerializable {
public function jsonSerialize(): mixed { // ✅ 必须写 : mixed
return ['name' => 'Alice'];
}
}
❌ 如果不写:
csharp
public function jsonSerialize() { ... }
⚠️ 触发弃用通知:
scala深色版本 Deprecated: Return type should be compatible with ...
✅ 解决方案:#[ReturnTypeWillChange]
用于兼容 PHP < 8.1 的库开发:
php
use ReturnTypeWillChange;
class MyData implements JsonSerializable {
#[ReturnTypeWillChange]
public function jsonSerialize() {
return ['name' => 'Alice'];
}
}
⚠️ 注意:这是临时方案,长期应加上正确返回类型。
七、::
操作符:静态世界的"遥控器"
✅ ::
的完整用法
用法 | 示例 | 说明 |
---|---|---|
静态属性 | ClassName::$prop |
必须带 $ |
静态方法 | ClassName::method() |
常见调用方式 |
常量 | ClassName::CONSTANT |
不带 $ |
父类调用 | parent::method() |
在子类中调用父类方法 |
自身类 | self::method() |
指向定义它的类 |
实际调用者 | static::method() |
后期静态绑定(重点!) |
八、self
vs static
:早期绑定 vs 后期静态绑定
🔍 核心区别
关键字 | 绑定时机 | 含义 | 特点 |
---|---|---|---|
self |
编译时(早期绑定) | "写这个方法的类" | 死板,不随调用者变 |
static |
运行时(后期绑定) | "实际调用这个方法的类" | 灵活,智能识别上下文 |
🍕 生活化比喻:总公司与分公司
php
class 总公司 {
protected static $logo = '蓝色地球';
public static function 显示Logo_A() {
echo self::$logo . "\n"; // ❌ 固定用总公司的
}
public static function 显示Logo_B() {
echo static::$logo . "\n"; // ✅ 谁调用,就用谁的
}
}
class 北京分公司 extends 总公司 {
protected static $logo = '北京天坛';
}
北京分公司::显示Logo_A(); // 输出:蓝色地球 ❌
北京分公司::显示Logo_B(); // 输出:北京天坛 ✅
✅
static
实现了"一套制度,多地执行"。
九、后期静态绑定(Late Static Binding)的应用场景
✅ 场景 1:通用模型 + 不同配置
scala
class Model {
protected static $table;
public static function find($id) {
return "SELECT * FROM " . static::$table . " WHERE id = $id";
}
}
class User extends Model { protected static $table = 'users'; }
class Post extends Model { protected static $table = 'posts'; }
User::find(1); // ✅ SELECT * FROM users ...
Post::find(2); // ✅ SELECT * FROM posts ...
✅ 场景 2:静态工厂模式
scala
class Animal {
public static function create() {
return new static(); // 返回调用者的实例
}
}
class Dog extends Animal {}
class Cat extends Animal {}
$dog = Dog::create(); // ✅ 返回 Dog 实例
$cat = Cat::create(); // ✅ 返回 Cat 实例
✅
new static()
是关键!
十、最佳实践与陷阱总结
建议 | 说明 |
---|---|
✅ 优先使用 static 而不是 self |
在静态方法中,尤其是父类方法 |
✅ 静态属性用 protected 或 private |
避免外部随意修改 |
✅ 文档说明静态方法行为 | 特别是涉及后期绑定时 |
❌ 避免在非静态方法中滥用 static:: |
容易混淆,优先用 $this-> |
✅ 使用 final class 防止继承 |
当你不希望类被扩展时 |
✅ 总结图谱
php
text
深色版本
PHP 继承与静态机制全景图
│
├── 继承:extends
│ ├── public / protected 成员可继承
│ ├── private 成员不继承(但同类实例可互访)
│ ├── 可见性只能放宽(LSP 原则)
│ └── 构造方法是唯一可收紧的例外
│
├── 只读属性:readonly
│ ├── 不能与普通属性互相覆盖
│ └── 子类可显式声明 readonly
│
├── 方法重写
│ ├── PHP 8.0+ 严格签名兼容
│ └── PHP 8.1+ 内部类返回类型必须匹配
│ └── 可用 #[ReturnTypeWillChange] 兼容旧版本
│
└── 静态机制
├── :: 操作符:访问静态成员
├── self:早期绑定(定义类)
└── static:后期静态绑定(调用类)
└── 实现"一套逻辑,多种配置"
📚 一句话口诀(终极复习)
"
extends
是父子关系,::
是遥控器,
self
是亲爹,static
是干爹;
谁调用,static
就跟谁走!"