構(gòu)造函數(shù)和析構(gòu)函數(shù)

構(gòu)造函數(shù)

__construct(mixed ...$values = ""): void

PHP 允許開發(fā)者在一個(gè)類中定義一個(gè)方法作為構(gòu)造函數(shù)。具有構(gòu)造函數(shù)的類會(huì)在每次創(chuàng)建新對(duì)象時(shí)先調(diào)用此方法,所以非常適合在使用對(duì)象之前做一些初始化工作。

注意: 如果子類中定義了構(gòu)造函數(shù)則不會(huì)隱式調(diào)用其父類的構(gòu)造函數(shù)。要執(zhí)行父類的構(gòu)造函數(shù),需要在子類的構(gòu)造函數(shù)中調(diào)用 parent::__construct()。如果子類沒(méi)有定義構(gòu)造函數(shù)則會(huì)如同一個(gè)普通的類方法一樣從父類繼承(假如沒(méi)有被定義為 private 的話)。

示例 #1 繼承中的構(gòu)造函數(shù)

<?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 的構(gòu)造函數(shù)
}

// In BaseClass constructor
$obj = new BaseClass();

// In BaseClass constructor
// In SubClass constructor
$obj = new SubClass();

// In BaseClass constructor
$obj = new OtherSubClass();
?>

與其它方法不同, __construct() 在繼承時(shí)不受簽名兼容性規(guī)則的約束。

自 PHP 5.3.3 起,在命名空間中,與類名同名的方法不再作為構(gòu)造函數(shù)。不使用命名空間中的類則不受影響。 構(gòu)造函數(shù)是一個(gè)普通的方法,在對(duì)應(yīng)對(duì)象實(shí)例化時(shí)自動(dòng)被調(diào)用。 因此可以定義任何數(shù)量的參數(shù),可以是必選、可以有類型、可以有默認(rèn)值。 構(gòu)造器的參數(shù)放在類名后的括號(hào)里調(diào)用。

示例 #2 使用構(gòu)造器參數(shù)

<?php
class Point {
    protected 
int $x;
    protected 
int $y;

    public function 
__construct(int $xint $y 0) {
        
$this->$x;
        
$this->$y;
    }
}

// 兩個(gè)參數(shù)都傳入
$p1 = new Point(45);
// 僅傳入必填的參數(shù)。 $y 會(huì)默認(rèn)取值 0。
$p2 = new Point(4);
// 使用命名參數(shù)(PHP 8.0 起):
$p3 = new Point(y5x4);
?>

如果一個(gè)類沒(méi)有構(gòu)造函數(shù),以及構(gòu)造函數(shù)的參數(shù)不是必填項(xiàng)時(shí),括號(hào)就可以省略。

舊式風(fēng)格的構(gòu)造器

PHP 8.0.0 之前,全局命名空間內(nèi)的類如果有一個(gè)同名的方法,則會(huì)解析為舊式風(fēng)格的構(gòu)造器。 雖然函數(shù)能被當(dāng)作構(gòu)造器,但該語(yǔ)法已被廢棄,并會(huì)導(dǎo)致 E_DEPRECATED 錯(cuò)誤。 如果 __construct() 和同名方法同時(shí)存在時(shí), 會(huì)調(diào)用 __construct()。

以下兩種情況時(shí),與類同名的方法不再有特殊意義:命名空間中的類、PHP 8.0.0 起的任何類。

新代碼中要使用 __construct()。

構(gòu)造器屬性提升

PHP 8.0.0 起,構(gòu)造器的參數(shù)也可以相應(yīng)提升為類的屬性。 構(gòu)造器的參數(shù)賦值給類屬性的行為很普遍,否則無(wú)法操作。 而構(gòu)造器提升的功能則為這種場(chǎng)景提供了便利。 因此上面的例子可以用以下方式重寫:

示例 #3 使用構(gòu)造器屬性提升

<?php
class Point {
    public function 
__construct(protected int $x, protected int $y 0) {
    }
}

當(dāng)構(gòu)造器參數(shù)帶訪問(wèn)控制(visibility modifier)時(shí),PHP 會(huì)同時(shí)把它當(dāng)作對(duì)象屬性和構(gòu)造器參數(shù), 并賦值到屬性。 構(gòu)造器可以是空的,或者包含其他語(yǔ)句。 參數(shù)值賦值到相應(yīng)屬性后執(zhí)行正文中額外的代碼語(yǔ)句。

并非所有參數(shù)都需要提升??梢曰旌咸嵘虿惶嵘齾?shù)作為屬性,也不需要按順序。 提升后的參數(shù)不影響構(gòu)造器內(nèi)代碼調(diào)用。

注意:

對(duì)象屬性的類型不能為 callable 以避免為引擎帶來(lái)混淆。 因此提升的參數(shù)也不能是 callable。 其他任意 類型聲明 是允許的。

注意:

放在構(gòu)造器提升參數(shù)里的屬性會(huì)同時(shí)復(fù)制為屬性和參數(shù)。

Static 創(chuàng)造方法

在 PHP 中每個(gè) class 只能有一個(gè)構(gòu)造器。 然而有些情況下,需要用不同的輸入實(shí)現(xiàn)不同的方式構(gòu)造對(duì)象。 這種情況下推薦使用 static 方法包裝構(gòu)造。

示例 #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 $idstring $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);

可以設(shè)置構(gòu)造器為 private 或 protected,防止自行額外調(diào)用。 這時(shí)只有 static 方法可以實(shí)例化一個(gè)類。 由于它們位于同一個(gè)定義的 class 因此可以訪問(wèn)私有方法,也不需要在同一個(gè)對(duì)象實(shí)例中。 當(dāng)然構(gòu)造器不一定要設(shè)置為 private,是否合理取決于實(shí)際情況。

三個(gè) static 方法展示了對(duì)象以不同方式的實(shí)例化方式。

  • fromBasicData() 把所需的全部參數(shù)傳入構(gòu)造器,創(chuàng)建對(duì)象并返回結(jié)果。
  • fromJson() 接受 JSON 字符串,,預(yù)處理成構(gòu)造器所需的格式,然后返回新的對(duì)象。
  • fromXml() 接受 XML 字符串并解析,然后創(chuàng)建一個(gè)單純的對(duì)象。 由于參數(shù)都是可選的,使得可以忽略所有參數(shù)去調(diào)用構(gòu)造器。然后為對(duì)象的屬性賦值后返回結(jié)果。

在以上三個(gè)例子中,static 關(guān)鍵詞會(huì)被翻譯成代碼所在類的類名。 這個(gè)例子中是 Product

析構(gòu)函數(shù)

__destruct(): void

PHP 有析構(gòu)函數(shù)的概念,這類似于其它面向?qū)ο蟮恼Z(yǔ)言,如 C++。析構(gòu)函數(shù)會(huì)在到某個(gè)對(duì)象的所有引用都被刪除或者當(dāng)對(duì)象被顯式銷毀時(shí)執(zhí)行。

示例 #5 析構(gòu)函數(shù)示例

<?php

class MyDestructableClass 
{
    function 
__construct() {
        print 
"In constructor\n";
    }

    function 
__destruct() {
        print 
"Destroying " __CLASS__ "\n";
    }
}

$obj = new MyDestructableClass();

和構(gòu)造函數(shù)一樣,父類的析構(gòu)函數(shù)不會(huì)被引擎暗中調(diào)用。要執(zhí)行父類的析構(gòu)函數(shù),必須在子類的析構(gòu)函數(shù)體中顯式調(diào)用 parent::__destruct()。此外也和構(gòu)造函數(shù)一樣,子類如果自己沒(méi)有定義析構(gòu)函數(shù)則會(huì)繼承父類的。

析構(gòu)函數(shù)即使在使用 exit() 終止腳本運(yùn)行時(shí)也會(huì)被調(diào)用。在析構(gòu)函數(shù)中調(diào)用 exit() 將會(huì)中止其余關(guān)閉操作的運(yùn)行。

注意:

析構(gòu)函數(shù)在腳本關(guān)閉時(shí)調(diào)用,此時(shí)所有的 HTTP 頭信息已經(jīng)發(fā)出。腳本關(guān)閉時(shí)的工作目錄有可能和在 SAPI(如 apache)中時(shí)不同。

注意:

試圖在析構(gòu)函數(shù)(在腳本終止時(shí)被調(diào)用)中拋出一個(gè)異常會(huì)導(dǎo)致致命錯(cuò)誤。