在PHP面向对象编程中,构造函数承担着对象“出生”时的初始化职责。每当用new关键字创建一个对象实例时,构造函数会被自动调用,无需手动触发。这个机制让开发者能够在对象投入使用之前,完成属性赋初值、建立数据库连接、注入依赖资源等准备工作。PHP从5.x版本开始正式引入__construct()作为构造函数的统一写法,替代了早期用与类同名的方法作为构造函数的旧式语法。
一、__construct()的基本使用
__construct()是一个特殊的方法,在类中定义后,每次实例化对象时PHP都会自动执行它。构造函数没有返回值(声明为void),但可以接受任意数量的参数。
语法结构
__construct(mixed ...$values = ""): void
参数列表按需定义,可以设置默认值让参数变为可选。
示例1:无参数的构造函数——统一赋初值
<?php
class Mobile {
// 成员变量
public $brand;
public $price;
// 构造函数,为所有对象设置相同的初始值
function __construct() {
$this->brand = "华为";
$this->price = 3999;
}
function getInfo() {
echo "品牌:{$this->brand},价格:{$this->price} 元\n";
}
}
$phone1 = new Mobile();
$phone2 = new Mobile();
$phone1->getInfo();
$phone2->getInfo();
?>
输出结果
品牌:华为,价格:3999 元
品牌:华为,价格:3999 元
这种写法适合所有对象初始状态一致的场景。但项目开发中,更多时候需要每个对象有不同的初始值,这就需要带参数的构造函数。
个人经验分享:无参构造函数在项目里用得不算多。比较常见的情况是类有一些合理的默认值(比如日志级别默认设为INFO、缓存过期时间默认3600秒),既允许使用默认值,也支持按需覆盖。这时候更合适的做法是带默认参数的构造函数,下面会讲到。
二、带参数的构造函数
在构造函数中声明参数,创建对象时传入具体值,可以让每个对象实例拥有个性化的初始状态。这比先创建对象再逐个调用setter方法的方式更简洁,也更能保证对象创建完成后立刻处于可用状态。
示例2:用构造函数参数分别初始化对象
<?php
class Book {
public $title;
public $author;
public $price;
// 带参数的构造函数
function __construct($bookTitle, $bookAuthor, $bookPrice) {
$this->title = $bookTitle;
$this->author = $bookAuthor;
$this->price = $bookPrice;
}
function getInfo() {
echo "《{$this->title}》作者:{$this->author},售价:{$this->price} 元\n";
}
}
$book1 = new Book("PHP面向对象实战", "陈老师", 69.80);
$book2 = new Book("深入理解计算机系统", "Randal E. Bryant", 139.00);
$book1->getInfo();
$book2->getInfo();
?>
输出结果
《PHP面向对象实战》作者:陈老师,售价:69.8 元
《深入理解计算机系统》作者:Randal E. Bryant,售价:139 元
个人建议:如果一个类有多个必填属性(少了任何一个对象就无常工作),把这些属性放在构造函数参数里,并把参数设为必传(不给默认值),可以在编译阶段就拦截掉缺少参数的调用。比如用户模块里的User类,用户名和邮箱是必填的,放在构造函数里强制要求,比给个空壳对象再让调用方自己记得设值要可靠不少。
三、用默认参数值模拟构造器重载
Java、C++等语言支持方法重载——同一个类里可以有多个同名但参数列表不同的构造函数。PHP在语言层面不支持这种传统的编译时重载,但可以通过给构造函数参数设置默认值来达到类似效果。
示例3:支持不同参数个数的构造函数
<?php
class Student {
public $name;
public $grade;
public $studentId;
// 三个参数都有默认值,调用时可选择传入部分参数
function __construct($name = "未知", $grade = "未分班", $studentId = "000000") {
$this->name = $name;
$this->grade = $grade;
$this->studentId = $studentId;
}
function getInfo() {
echo "姓名:{$this->name},班级:{$this->grade},学号:{$this->studentId}\n";
}
}
// 传三个参数
$stu1 = new Student("张小明", "三年级1班", "2026001");
// 只传姓名
$stu2 = new Student("李华");
// 不传任何参数
$stu3 = new Student();
$stu1->getInfo();
$stu2->getInfo();
$stu3->getInfo();
?>
输出结果
姓名:张小明,班级:三年级1班,学号:2026001
姓名:李华,班级:未分班,学号:000000
姓名:未知,班级:未分班,学号:000000
这种方式在开发中很实用。比如在从数据库查询结果构建对象时,有些字段可能为空,用默认值可以让对象在任何情况下都有一个合法状态,避免后续代码中频繁的null判断。
四、构造函数中的类型声明
PHP 7.0引入了标量类型声明,从PHP 7.4开始类属性也可以声明类型,到了PHP 8.0又加入了联合类型和mixed类型。在构造函数中对参数做类型声明,能让代码意图更明确,也能在传入错误类型时更早地发现问题。
示例4:带类型声明的构造函数
<?php
class Product {
// 属性类型声明(PHP 7.4+)
public string $name;
public float $price;
public int $stock;
// 构造函数参数类型声明
function __construct(string $name, float $price, int $stock) {
$this->name = $name;
$this->price = $price;
$this->stock = $stock;
}
function getInfo() {
echo "商品:{$this->name},单价:{$this->price} 元,库存:{$this->stock} 件\n";
}
}
$prod1 = new Product("机械键盘", 299.00, 50);
$prod2 = new Product("无线鼠标", 89.90, 120);
$prod1->getInfo();
$prod2->getInfo();
?>
输出结果
商品:机械键盘,单价:299 元,库存:50 件
商品:无线鼠标,单价:89.9 元,库存:120 件
如果尝试传入类型不匹配的参数,比如new Product("键盘", "二百元", 10),PHP会抛出TypeError,在严格类型模式下尤其严格。这比等到业务逻辑运行到一半才发现数据类型不对要安全得多。
个人见解:在团队协作的项目里,构造函数类型声明加上属性的类型声明,相当于给类的接口加了一层“自动文档”。其他开发者在调用时,IDE能根据类型声明给出智能提示,传错类型也会有即时反馈。我目前在写新代码时,会尽量给所有构造函数参数和类属性都加上类型声明,这一习惯减少了不少低级的类型错误。
五、子类构造函数与parent::__construct()
当子类定义了自己的构造函数时,父类的构造函数不会被自动调用,需要在子类构造方法中显式地用parent::__construct()来调用。这个设计给了开发者在子类中灵活控制父类初始化时机的空间。
示例5:子类中调用父类构造函数
<?php
class Device {
public $brand;
function __construct($brand) {
$this->brand = $brand;
echo "Device 构造函数执行,品牌:{$this->brand}\n";
}
}
class Laptop extends Device {
public $model;
function __construct($brand, $model) {
// 显式调用父类构造函数
parent::__construct($brand);
$this->model = $model;
echo "Laptop 构造函数执行,型号:{$this->model}\n";
}
}
$laptop = new Laptop("联想", "ThinkPad X1 Carbon");
?>
输出结果
Device 构造函数执行,品牌:联想
Laptop 构造函数执行,型号:ThinkPad X1 Carbon
先执行父类构造、再执行子类构造,这个顺序符合直觉。如果子类构造函数忘记调用parent::__construct(),父类的初始化逻辑就被跳过了,可能导致父类定义的属性处于未初始化状态。在继承体系比较深或者父类构造函数做了重要资源初始化(比如数据库连接)的情况下,这个细节值得特别注意。
本节课程知识要点
-
构造函数
__construct()在对象实例化时自动调用,负责为对象设置初始状态,不能有返回值。 -
带参数的构造函数可以在创建对象时直接传入个性化数据,比创建空对象再逐个赋值更简洁可靠。
-
PHP不支持传统的方法重载,但可以通过给构造参数设置默认值来模拟不同参数个数的调用方式。
-
在构造函数中使用类型声明(
string、int、float等),能提前拦截类型错误,配合IDE智能提示提升开发效率。 -
子类定义了构造函数后,父类构造函数不会被自动调用,需要显式使用
parent::__construct()来执行父类初始化逻辑。 -
把对象正常工作所必需的依赖和属性放在构造函数中强制要求,非必需的配置项可以给默认值或通过setter方法设置,这是构建健壮对象的基本思路。
-
PHP早期版本支持用与类同名的方法作为构造函数,这种写法在PHP 7.x中已标记为弃用,PHP 8中正式移除,所有新代码都应使用
__construct()。
构造函数在PHP面向对象编程中占据着比较基础的位置。从最简单的无参构造,到带必填参数和类型声明的构造方式,再到子类继承时对父类构造函数的显式调用,这些内容贯穿了对象的整个生命周期管理。掌握好构造函数的各种用法,能够让你在设计类的时候思路更清晰,写出来的代码在扩展和维护时也会更加顺手。