本文目录导读:

在 PHP 中,构造函数(__construct())和析构函数(__destruct())的执行顺序有明确的规则,尤其是在继承和对象生命周期的上下文中。
单个类的基础顺序
对于单一类,顺序非常直接:
class MyClass {
public function __construct() {
echo "构造执行\n";
}
public function __destruct() {
echo "析构执行\n";
}
}
$obj = new MyClass();
echo "对象使用中\n";
unset($obj); // 或脚本结束
// 输出:
// 构造执行
// 对象使用中
// 析构执行
规则:构造 → 使用 → 析构
继承关系中的顺序(重点)
当存在继承时,PHP 不会自动调用父类的构造/析构方法,需要显式调用(通常通过 parent::)。
1 标准执行顺序
class ParentClass {
public function __construct() {
echo "父类构造\n";
}
public function __destruct() {
echo "父类析构\n";
}
}
class ChildClass extends ParentClass {
public function __construct() {
parent::__construct(); // 显式调用
echo "子类构造\n";
}
public function __destruct() {
echo "子类析构\n";
parent::__destruct(); // 显式调用
}
}
$obj = new ChildClass();
unset($obj);
// 输出:
// 父类构造
// 子类构造
// 子类析构
// 父类析构
构造顺序:父类 → 子类(类似叠箱子,从外到内构建基础)
析构顺序:子类 → 父类(与构造相反,后构建的先销毁)
不调用 parent:: 的情况
class ChildWithoutParentCall extends ParentClass {
public function __construct() {
echo "子类构造\n";
// 没有调用 parent::__construct()
}
}
$obj = new ChildWithoutParentCall();
// 输出:
// 子类构造 ← 父类构造不会自动执行
PHP 不会自动调用父类的构造/析构方法,必须显式调用。
多个对象 + 引用计数的析构顺序
当多个对象相互引用时,析构顺序取决于引用计数归零的时间:
class ObjA {
public function __destruct() {
echo "A 析构\n";
}
}
class ObjB {
public $a;
public function __destruct() {
echo "B 析构\n";
}
}
$a = new ObjA();
$b = new ObjB();
$b->a = $a; // A 的引用计数增加
unset($a); // 这里不会触发 A 的析构,因为 B 还引用它
unset($b); // 先析构 B,A 的引用计数归零,再析构 A
// 输出:
// B 析构
// A 析构
引用计数归零的先后顺序决定析构顺序。
静态属性与全局变量的析构顺序
class StaticExample {
public function __destruct() {
echo "Static 析构\n";
}
}
$globalObj = new StaticExample();
function test() {
$localObj = new StaticExample();
}
test(); // 局部变量在函数结束后析构
echo "脚本结束前\n";
// 输出:
// Static 析构 ← 局部变量
// 脚本结束前
// Static 析构 ← 全局变量(在脚本结束时析构)
规则:
- 局部变量:函数/方法结束时析构
- 全局变量:脚本结束时析构(顺序按变量定义顺序,但受引用影响)
- 静态变量:在全局变量之后析构
核心原则总结
| 场景 | 构造顺序 | 析构顺序 |
|---|---|---|
继承(显式调用 parent::) |
父类 → 子类 | 子类 → 父类(后进先出) |
继承(不调用 parent::) |
只执行子类构造 | 只执行子类析构 |
| 多个独立对象 | 按 new 顺序 | 按 unset/引用计数归零顺序 |
| 全局 vs 局部对象 | 局部优先于全局 |
最佳实践:
- 在子类构造中始终先调用
parent::__construct() - 在子类析构中始终先执行子类逻辑,再调用
parent::__destruct() - 避免在析构函数中抛出异常(PHP 8.0+ 会将其转为致命错误)
记忆口诀
构造像搭积木,从地基往上垒
析构像拆建筑,先拆顶层再拆底