PHP 构造函数与析构函数:从基础到高级的完整指南
一、构造函数:对象的初始化仪式
✅ 1. 基础语法与作用
构造函数在创建对象时自动执行,用于初始化对象属性。
class Person {
public $name;
public $age;
// 构造函数
public function __construct($name, $age) {
$this->name = $name;
$this->age = $age;
echo "创建了 {$name},年龄 {$age}\n";
}
}
// 创建对象时自动调用构造函数
$p1 = new Person("张三", 25); // 输出:创建了 张三,年龄 25
✅ 2. PHP 8.0+ 构造器属性提升(Constructor Property Promotion)
传统写法(繁琐)
class Point {
public $x;
public $y;
public $z;
public function __construct($x, $y, $z) {
$this->x = $x;
$this->y = $y;
$this->z = $z;
}
}
新写法(简洁)
class Point {
public function __construct(
public $x,
public $y,
public $z
) {
// 属性自动创建并赋值,构造函数体可为空
}
}
支持的修饰符:
public
protected
private
readonly
(PHP 8.1+)
class Product {
public function __construct(
public readonly int $id,
public string $name,
protected float $price
) {
// id 是只读的,创建后不能修改
}
}
✅ 3. 参数与默认值
class Rectangle {
public function __construct(
public float $width = 1.0,
public float $height = 1.0
) {
// 支持默认值
}
}
// 使用默认值
$rect1 = new Rectangle(); // width=1.0, height=1.0
$rect2 = new Rectangle(5.0); // width=5.0, height=1.0
$rect3 = new Rectangle(3.0, 4.0); // width=3.0, height=4.0
✅ 4. 继承中的构造函数
重要规则:子类必须手动调用父类构造函数
class Animal {
public function __construct(protected string $name) {
echo "动物 {$this->name} 已创建\n";
}
}
class Dog extends Animal {
public function __construct(
string $name,
private string $breed
) {
// ⚠️ 必须手动调用父类构造函数
parent::__construct($name);
echo "品种: {$this->breed}\n";
}
}
$dog = new Dog('旺财', '金毛');
// 输出:
// 动物 旺财 已创建
// 品种: 金毛
父类无构造函数时
class Animal {
// 没有构造函数
}
class Dog extends Animal {
public function __construct(string $name) {
// 不需要调用 parent::__construct()
echo "狗 {$name} 已创建\n";
}
}
✅ 5. 静态工厂方法(Static Factory Methods)
用于替代复杂构造逻辑,提高代码可读性。
class Product {
private function __construct(
private ?int $id,
private ?string $name
) {}
// 静态工厂方法
public static function fromBasicData(int $id, string $name): static {
return new static($id, $name);
}
public static function fromJson(string $json): static {
$data = json_decode($json, true);
return new static($data['id'], $data['name']);
}
public static function createEmpty(): static {
return new static(null, null);
}
}
// 使用示例
$product1 = Product::fromBasicData(1, '手机');
$product2 = Product::fromJson('{"id":2,"name":"电脑"}');
$product3 = Product::createEmpty();
二、析构函数:对象的清理仪式
✅ 1. 基础语法与作用
析构函数在对象销毁时自动执行,用于清理资源。
class FileHandler {
private $file;
public function __construct(string $filename) {
$this->file = fopen($filename, 'r');
echo "文件 {$filename} 已打开\n";
}
public function __destruct() {
if ($this->file) {
fclose($this->file);
echo "文件已关闭\n";
}
}
}
// 使用示例
$handler = new FileHandler('data.txt');
// 当 $handler 超出作用域或被 unset 时
// 自动执行 __destruct()
✅ 2. 执行时机
场景1:脚本结束
$obj = new MyClass();
// 脚本结束时自动调用 __destruct()
场景2:unset()
函数
$obj = new MyClass();
unset($obj); // 立即调用 __destruct()
场景3:变量超出作用域
function test() {
$obj = new MyClass(); // 创建
// 函数结束时 $obj 被销毁,调用 __destruct()
}
test();
场景4:exit()
时
$obj = new MyClass();
exit(); // 仍会执行 __destruct()
✅ 3. 多对象销毁顺序
class Test {
public function __construct(private string $name) {}
public function __destruct() {
echo "销毁 {$this->name}\n";
}
}
$a = new Test('A');
$b = new Test('B');
$c = new Test('C');
// 输出顺序(后创建的先销毁):
// 销毁 C
// 销毁 B
// 销毁 A
❌ 4. 重要限制与注意事项
限制1:不能抛出异常
public function __destruct() {
// ❌ 致命错误!
// throw new Exception("清理失败");
// ✅ 正确做法:记录日志
error_log("清理失败: " . $e->getMessage());
}
限制2:不能有参数
// ❌ 错误
// public function __destruct($param) {}
限制3:不能是静态方法
// ❌ 错误
// public static function __destruct() {}
注意:可能不执行的情况
- 脚本被
die()
或exit()
强制终止 - 发生死循环
- 服务器崩溃
三、特殊场景与最佳实践
✅ 1. 单例模式中的构造函数
class Singleton {
private static ?Singleton $instance = null;
// 私有构造函数,防止外部创建
private function __construct() {}
public static function getInstance(): Singleton {
if (self::$instance === null) {
self::$instance = new self();
}
return self::$instance;
}
// 私有克隆函数
private function __clone() {}
}
✅ 2. 克隆对象时的构造
class Person {
public function __construct(public string $name) {
echo "构造: {$this->name}\n";
}
public function __clone() {
// 克隆时不会调用构造函数
echo "克隆: {$this->name}\n";
}
}
$p1 = new Person("张三"); // 输出:构造: 张三
$p2 = clone $p1; // 输出:克隆: 张三
✅ 3. 与序列化的配合
class User {
public function __construct(public string $name) {}
public function __sleep() {
// 序列化前执行
return ['name'];
}
public function __wakeup() {
// 反序列化后执行(类似构造函数)
echo "反序列化: {$this->name}\n";
}
}
$user = new User("李四");
$serialized = serialize($user);
$unserialized = unserialize($serialized); // 输出:反序列化: 李四
四、常见错误与调试
❌ 错误1:忘记调用父类构造函数
class Dog extends Animal {
public function __construct(string $name, string $breed) {
// ❌ 忘记调用 parent::__construct($name)
$this->breed = $breed;
}
}
❌ 错误2:析构函数抛出异常
public function __destruct() {
// ❌ 这样会中断脚本
// throw new Exception("Error");
}
✅ 调试技巧
class DebugClass {
public function __construct() {
error_log("对象创建");
}
public function __destruct() {
error_log("对象销毁");
}
}
五、终极总结
特性 | 说明 |
---|---|
构造函数 | __construct() ,PHP 8.0+ 支持属性提升 |
参数默认值 | 支持字面量和常量 |
继承规则 | 子类必须手动调用 parent::__construct() |
析构函数 | __destruct() 用于资源清理 |
执行时机 | 对象销毁时(脚本结束、unset、超出作用域) |
重要限制 | 析构函数不能抛异常、不能有参数 |
✅ 最佳实践:
- 使用构造器属性提升减少样板代码
- 析构函数只用于资源清理(文件、数据库连接等)
- 复杂对象创建使用静态工厂方法
- 子类构造函数必须调用父类构造函数
- 析构函数不要抛出异常,用日志记录错误
🚀 记住:构造函数是对象的"出生证明",析构函数是对象的"告别仪式",合理使用它们能让代码更健壮!