PHP 允許開(kāi)發(fā)者在一個(gè)類(lèi)中定義一個(gè)方法作為構造函數。具有構造函數的類(lèi)會(huì )在每次創(chuàng )建新對象時(shí)先調用此方法,所以非常適合在使用對象之前做一些初始化工作。
注意: 如果子類(lèi)中定義了構造函數則不會(huì )隱式調用其父類(lèi)的構造函數。要執行父類(lèi)的構造函數,需要在子類(lèi)的構造函數中調用 parent::__construct()。如果子類(lèi)沒(méi)有定義構造函數則會(huì )如同一個(gè)普通的類(lèi)方法一樣從父類(lèi)繼承(假如沒(méi)有被定義為 private 的話(huà))。
示例 #1 繼承中的構造函數
<?php
class BaseClass {
function __construct() {
print "In BaseClass constructor\n";
}
}
class SubClass extends BaseClass {
function __construct() {
parent::__construct();
print "In SubClass constructor\n";
}
}
class OtherSubClass extends BaseClass {
// 繼承 BaseClass 的構造函數
}
// In BaseClass constructor
$obj = new BaseClass();
// In BaseClass constructor
// In SubClass constructor
$obj = new SubClass();
// In BaseClass constructor
$obj = new OtherSubClass();
?>
與其它方法不同, __construct() 在繼承時(shí)不受簽名兼容性規則的約束。
自 PHP 5.3.3 起,在命名空間中,與類(lèi)名同名的方法不再作為構造函數。不使用命名空間中的類(lèi)則不受影響。 構造函數是一個(gè)普通的方法,在對應對象實(shí)例化時(shí)自動(dòng)被調用。 因此可以定義任何數量的參數,可以是必選、可以有類(lèi)型、可以有默認值。 構造器的參數放在類(lèi)名后的括號里調用。
示例 #2 使用構造器參數
<?php
class Point {
protected int $x;
protected int $y;
public function __construct(int $x, int $y = 0) {
$this->x = $x;
$this->y = $y;
}
}
// 兩個(gè)參數都傳入
$p1 = new Point(4, 5);
// 僅傳入必填的參數。 $y 會(huì )默認取值 0。
$p2 = new Point(4);
// 使用命名參數(PHP 8.0 起):
$p3 = new Point(y: 5, x: 4);
?>
如果一個(gè)類(lèi)沒(méi)有構造函數,以及構造函數的參數不是必填項時(shí),括號就可以省略。
PHP 8.0.0 之前,全局命名空間內的類(lèi)如果有一個(gè)同名的方法,則會(huì )解析為舊式風(fēng)格的構造器。
雖然函數能被當作構造器,但該語(yǔ)法已被廢棄,并會(huì )導致 E_DEPRECATED
錯誤。
如果 __construct() 和同名方法同時(shí)存在時(shí),
會(huì )調用 __construct()。
以下兩種情況時(shí),與類(lèi)同名的方法不再有特殊意義:命名空間中的類(lèi)、PHP 8.0.0 起的任何類(lèi)。
新代碼中要使用 __construct()。
PHP 8.0.0 起,構造器的參數也可以相應提升為類(lèi)的屬性。 構造器的參數賦值給類(lèi)屬性的行為很普遍,否則無(wú)法操作。 而構造器提升的功能則為這種場(chǎng)景提供了便利。 因此上面的例子可以用以下方式重寫(xiě):
示例 #3 使用構造器屬性提升
<?php
class Point {
public function __construct(protected int $x, protected int $y = 0) {
}
}
當構造器參數帶訪(fǎng)問(wèn)控制(visibility modifier)時(shí),PHP 會(huì )同時(shí)把它當作對象屬性和構造器參數, 并賦值到屬性。 構造器可以是空的,或者包含其他語(yǔ)句。 參數值賦值到相應屬性后執行正文中額外的代碼語(yǔ)句。
并非所有參數都需要提升??梢曰旌咸嵘虿惶嵘齾底鳛閷傩?,也不需要按順序。 提升后的參數不影響構造器內代碼調用。
注意:
對象屬性的類(lèi)型不能為 callable 以避免為引擎帶來(lái)混淆。 因此提升的參數也不能是 callable。 其他任意 類(lèi)型聲明 是允許的。
注意:
放在構造器提升參數里的屬性會(huì )同時(shí)復制為屬性和參數。
在 PHP 中每個(gè) class 只能有一個(gè)構造器。 然而有些情況下,需要用不同的輸入實(shí)現不同的方式構造對象。 這種情況下推薦使用 static 方法包裝構造。
示例 #4 使用 static 創(chuàng )造方法
<?php
class Product {
private ?int $id;
private ?string $name;
private function __construct(?int $id = null, ?string $name = null) {
$this->id = $id;
$this->name = $name;
}
public static function fromBasicData(int $id, string $name): static {
$new = new static($id, $name);
return $new;
}
public static function fromJson(string $json): static {
$data = json_decode($json);
return new static($data['id'], $data['name']);
}
public static function fromXml(string $xml): static {
// 自定義代碼邏輯。
$data = convert_xml_to_array($xml);
$new = new static();
$new->id = $data['id'];
$new->name = $data['name'];
return $new;
}
}
$p1 = Product::fromBasicData(5, 'Widget');
$p2 = Product::fromJson($some_json_string);
$p3 = Product::fromXml($some_xml_string);
可以設置構造器為 private 或 protected,防止自行額外調用。 這時(shí)只有 static 方法可以實(shí)例化一個(gè)類(lèi)。 由于它們位于同一個(gè)定義的 class 因此可以訪(fǎng)問(wèn)私有方法,也不需要在同一個(gè)對象實(shí)例中。 當然構造器不一定要設置為 private,是否合理取決于實(shí)際情況。
三個(gè) static 方法展示了對象以不同方式的實(shí)例化方式。
fromBasicData()
把所需的全部參數傳入構造器,創(chuàng )建對象并返回結果。fromJson()
接受 JSON 字符串,,預處理成構造器所需的格式,然后返回新的對象。fromXml()
接受 XML 字符串并解析,然后創(chuàng )建一個(gè)單純的對象。
由于參數都是可選的,使得可以忽略所有參數去調用構造器。然后為對象的屬性賦值后返回結果。
在以上三個(gè)例子中,static
關(guān)鍵詞會(huì )被翻譯成代碼所在類(lèi)的類(lèi)名。
這個(gè)例子中是 Product
。
PHP 有析構函數的概念,這類(lèi)似于其它面向對象的語(yǔ)言,如 C++。析構函數會(huì )在到某個(gè)對象的所有引用都被刪除或者當對象被顯式銷(xiāo)毀時(shí)執行。
示例 #5 析構函數示例
<?php
class MyDestructableClass
{
function __construct() {
print "In constructor\n";
}
function __destruct() {
print "Destroying " . __CLASS__ . "\n";
}
}
$obj = new MyDestructableClass();
和構造函數一樣,父類(lèi)的析構函數不會(huì )被引擎暗中調用。要執行父類(lèi)的析構函數,必須在子類(lèi)的析構函數體中顯式調用 parent::__destruct()。此外也和構造函數一樣,子類(lèi)如果自己沒(méi)有定義析構函數則會(huì )繼承父類(lèi)的。
析構函數即使在使用 exit() 終止腳本運行時(shí)也會(huì )被調用。在析構函數中調用 exit() 將會(huì )中止其余關(guān)閉操作的運行。
注意:
析構函數在腳本關(guān)閉時(shí)調用,此時(shí)所有的 HTTP 頭信息已經(jīng)發(fā)出。腳本關(guān)閉時(shí)的工作目錄有可能和在 SAPI(如 apache)中時(shí)不同。
注意:
試圖在析構函數(在腳本終止時(shí)被調用)中拋出一個(gè)異常會(huì )導致致命錯誤。