コンストラクタとデストラクタ

コンストラクタ

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

PHP では、開発者がクラスのコンストラクタメソッドを宣言することが できます。コンストラクタメソッドを有するクラスは、新たにオブジェクトが 生成される度にこのメソッドをコールします。これにより、 そのオブジェクトを使用する前に必要な初期化を行うことができます。

注意: 子クラスがコンストラクタを有している場合、親クラスのコンストラクタが 暗黙の内にコールされることはありません。 親クラスのコンストラクタを実行するには、子クラスのコンストラクタの 中で parent::__construct をコールすることが 必要です。 子クラスでコンストラクタを定義していない場合は、親クラスのコンストラクタを継承します (ただし、private 宣言されている場合は除く)。 これは、通常のクラスメソッドと同様です。

例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() を子クラスでオーバライドしても、 シグネチャの互換性に関するルール は適用されません。

コンストラクタは、対応するオブジェクトを初期化する間に呼び出されるメソッドです。 よって、任意の数の引数を取ることが出来ます。 この引数は必須にすることもできますし、型宣言もできますし、デフォルト値を取ったりすることもできます。 コンストラクタの引数は、クラス名の後の括弧に、引数を置くことで指定することが出来ます。

例2 コンストラクタを引数と一緒に使う

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

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

// 引数を両方渡す
$p1 = new Point(4, 5);
// 必須の引数のみを渡す。$y はデフォルト値0になります。
$p2 = new Point(4);
// 名前付き引数(PHP 8.0 以降):
$p3 = new Point(y: 5, x: 4);
?>

クラスにコンストラクタが存在しない場合、あるいは、コンストラクタに必須の引数がない場合、 括弧は省略できます。

古いスタイルのコンストラクタ

PHP 8.0.0 より前のバージョンでは、グローバル名前空間にあるクラスは、 クラス名と同じ名前のメソッドが古いスタイルのコンストラクタとして解釈されます。 この文法は推奨されておらず、E_DEPRECATED が発生するものの、 まだこの関数をコンストラクタとして呼び出すことが出来ます。 __construct() とクラス名と同じ名前のメソッドが両方定義されていた場合は、 __construct() がコンストラクタとして呼び出されます。

名前空間の中に存在するクラスについては、 PHP 8.0.0 以降では、 クラス名と同じ名前のメソッドはなんの意味も持ちません。

新しいコードでは、常に __construct() を使うようにしましょう。

コンストラクタのプロモーション

PHP 8.0.0 以降では、コンストラクタの引数を 対応するオブジェクトのプロパティに昇格させることができます。 コンストラクタの引数をプロパティに代入し、それ以外の操作を行わないことはよくあることです。 コンストラクタのプロモーションは、こういった場合の短縮記法を提供します。 「コンストラクタを引数と一緒に使う」の例は、次のように書き直すことが出来ます。

例3 コンストラクタのプロモーションを使う

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

コンストラクタの引数に修飾子が含まれている場合、 PHP はそれをオブジェクトのプロパティ、かつコンストラクタの引数であると解釈します。 そして、その引数の値をプロパティに代入します。 コンストラクタの本体は空にすることもできますし、 他の文を含めることも出来ます。 引数の値が対応するプロパティに代入された後、 追加の文が実行されます。

全ての引数をプロパティに昇格させる必要はありません。 昇格させる引数と、させない引数を混ぜることもできます。 プロパティに昇格した引数は、コンストラクタの呼び出しコードになんの影響も与えません。

注意:

アクセス権 の修飾子 (public, protected, private) を使うことが、プロパティの昇格を行わせるもっとも適したやり方ですが、 他にも (readonly のような) 単一の修飾子を使っても、 同じ昇格の効果が得られます。

注意:

PHP のエンジンが曖昧になってしまうため、 オブジェクトのプロパティは、callable 型にすることは出来ません。 そのため、昇格させる引数も callable 型にはできません。 しかし、それ以外の 型宣言 は許されています。

注意:

コンストラクタで昇格したプロパティはプロパティと関数のパラメータ両方に戻されるので、プロパティとパラメータの両方に対するあらゆる命名規則が適用されます。

注意:

昇格したコンストラクタの引数に指定された アトリビュート は、 プロパティと引数の両方にコピーされます。 昇格したコンストラクタの引数に指定されたデフォルト値は、 引数にだけコピーされます。プロパティにはコピーされません。

初期化時の new キーワード

PHP 8.1.0 以降では、 引数のデフォルト値の初期化時、 static 変数の初期化時、 グローバルな定数の初期化時に、 new を指定したオブジェクトが使えます。 同じものを、アトリビュートの引数や define に渡せるようにもなっています。

注意:

この文脈で、動的なクラス名、文字列でないクラス名、 無名クラスを指定することはできません。 また、配列で引数を展開させることはできません。 未サポートの式を引数に指定することもできません。

例4 初期化時に new キーワードを使う

<?php

// 以下は全て許可されます
static $x = new Foo;

const C = new Foo;
 
function test($param = new Foo) {}
 
#[AnAttribute(new Foo)]
class Test {
    public function __construct(
        public $prop = new Foo,
    ) {}
}

// 以下はいずれも許されません(コンパイルエラーになります)
function test(
    $a = new (CLASS_NAME_CONSTANT)(), // 動的なクラス名
    $b = new class {}, // 無名クラス
    $c = new A(...[]), // 引数の展開
    $d = new B($abc), // 定数式の展開はサポートされていません
) {}
?>

static な生成メソッド

PHP は、クラスひとつにつき、コンストラクタをひとつだけサポートしています。 しかし、場合によっては異なる入力を使い、 異なるやり方でオブジェクトを生成させるのが望ましい場合もあります。 その場合におすすめなのが、staticメソッドをコンストラクタのラッパーとして使うことです。

例5 static な生成メソッドを使う

<?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, true);
        return new static($data['id'], $data['name']);
    }

    public static function fromXml(string $xml): static {
        // Custom logic here.
        $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 にしておきます。 この場合、staticメソッドだけがクラスをインスタンス化するのに使えます。 なぜなら、staticメソッドは、同じオブジェクトのインスタンスでなくとも、 private なメソッドにアクセスできる同じクラスのメソッド定義として存在するからです。 コンストラクタを private にすることはオプションです。使い方によっては意味がないかもしれません...

上の public なstaticメソッドは、オブジェクトをインスタンス化する3つの異なるやり方を示しています。

  • fromBasicData() は、必要となる必須の引数を取り、 コンストラクタを呼ぶことでオブジェクトを生成し、その結果を返します。
  • fromJson() は、JSON文字列を受け取り、 コンストラクタで必要なフォーマットに変換するための前処理を行います。 その上で、新しいオブジェクトを返します。
  • fromXml() は、XML文字列を受け取り、前処理をします。 そして生のオブジェクトを生成します。コンストラクタは呼び出されていますが、 全ての引数はオプションなので、その実行はスキップされます。 結果を返す前に、オブジェクトのプロパティに直接値を代入しています。

上の3つの場合全てで、static キーワードはコードが存在するクラスの名前として解釈されます。 この場合は Product です。

デストラクタ

void __destruct()

PHP には、C++ のような他のオブジェクト指向言語に似たデストラクタの概念があります。 デストラクタメソッドは、 特定のオブジェクトを参照するリファレンスがひとつもなくなったときにコールされます。 あるいは、スクリプトの終了時にも順不同でコールされます。

例6 デストラクタの例

<?php

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

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

$obj = new MyDestructableClass();

コンストラクタと同様、親クラスのデストラクタがエンジンにより暗黙のうちに コールされるということはありません。親クラスのデストラクタを実行するには、 デストラクタの中で明示的に parent::__destruct をコールする必要があります。 また、コンストラクタと同様、子クラスでデストラクタを定義していない場合は 親クラスのデストラクタを継承します。

exit でスクリプトの実行を止めた場合にもデストラクタはコールされます。 デストラクタの内部で exit をコールすると、 それ以降のシャットダウンルーチンを実行しません。

注意:

スクリプトのシャットダウン時にデストラクタがコールされた場合は、 HTTP ヘッダはすでに送信されています。スクリプトのシャットダウン時の作業ディレクトリは、 SAPI によっては (たとえば Apache など) 異なります。

注意:

デストラクタの中から (スクリプトの終了処理時に) 例外をスローしようとすると、致命的なエラーを引き起こします。