PHPのお勉強!

PHP TOP

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

コンストラクタ

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

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 です。

デストラクタ

__destruct(): void

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 など) 異なります。

注意:

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

add a note

User Contributed Notes 14 notes

up
158
david dot scourfield at llynfi dot co dot uk
13 years ago
Be aware of potential memory leaks caused by circular references within objects. The PHP manual states "[t]he destructor method will be called as soon as all references to a particular object are removed" and this is precisely true: if two objects reference each other (or even if one object has a field that points to itself as in $this->foo = $this) then this reference will prevent the destructor being called even when there are no other references to the object at all. The programmer can no longer access the objects, but they still stay in memory.

Consider the following example:

<?php

header
("Content-type: text/plain");

class
Foo {

/**
* An indentifier
* @var string
*/
private $name;
/**
* A reference to another Foo object
* @var Foo
*/
private $link;

public function
__construct($name) {
$this->name = $name;
}

public function
setLink(Foo $link){
$this->link = $link;
}

public function
__destruct() {
echo
'Destroying: ', $this->name, PHP_EOL;
}
}

// create two Foo objects:
$foo = new Foo('Foo 1');
$bar = new Foo('Foo 2');

// make them point to each other
$foo->setLink($bar);
$bar->setLink($foo);

// destroy the global references to them
$foo = null;
$bar = null;

// we now have no way to access Foo 1 or Foo 2, so they OUGHT to be __destruct()ed
// but they are not, so we get a memory leak as they are still in memory.
//
// Uncomment the next line to see the difference when explicitly calling the GC:
// gc_collect_cycles();
//
// see also: http://www.php.net/manual/en/features.gc.php
//

// create two more Foo objects, but DO NOT set their internal Foo references
// so nothing except the vars $foo and $bar point to them:
$foo = new Foo('Foo 3');
$bar = new Foo('Foo 4');

// destroy the global references to them
$foo = null;
$bar = null;

// we now have no way to access Foo 3 or Foo 4 and as there are no more references
// to them anywhere, their __destruct() methods are automatically called here,
// BEFORE the next line is executed:

echo 'End of script', PHP_EOL;

?>

This will output:

Destroying: Foo 3
Destroying: Foo 4
End of script
Destroying: Foo 1
Destroying: Foo 2

But if we uncomment the gc_collect_cycles(); function call in the middle of the script, we get:

Destroying: Foo 2
Destroying: Foo 1
Destroying: Foo 3
Destroying: Foo 4
End of script

As may be desired.

NOTE: calling gc_collect_cycles() does have a speed overhead, so only use it if you feel you need to.
up
5
Hayley Watson
1 year ago
There are other advantages to using static factory methods to wrap object construction instead of bare constructor calls.

As well as allowing for different methods to use in different scenarios, with more relevant names both for the methods and the parameters and without the constructor having to handle different sets of arguments of different types:

* You can do all your input validation before attempting to construct the object.
* The object itself can bypass that input validation when constructing new instances of its own class, since you can ensure that it knows what it's doing.
* With input validation/preprocessing moved to the factory methods, the constructor itself can often be reduced to "set these properties to these arguments", meaning the constructor promotion syntax becomes more useful.
* Having been hidden away from users, the constructor's signature can be a bit uglier without becoming a pain for them. Heh.
* Static methods can be lifted and passed around as first class closures, to be called in the normal fashion wherever functions can be called, without the special "new" syntax.
* The factory method need not return a new instance of that exact class. It could return a pre-existing instance that would do the same job as the new one would (especially useful in the case of immutable "value type" objects by reducing duplication); or a simpler or more specific subclass to do the job with less overhead than a more generic instance of the original class. Returning a subclass means LSP still holds.
up
31
domger at freenet dot de
7 years ago
The __destruct magic method must be public.

public function __destruct()
{
;
}

The method will automatically be called externally to the instance. Declaring __destruct as protected or private will result in a warning and the magic method will not be called.

Note: In PHP 5.3.10 i saw strange side effects while some Destructors were declared as protected.
up
25
spleen
16 years ago
It's always the easy things that get you -

Being new to OOP, it took me quite a while to figure out that there are TWO underscores in front of the word __construct.

It is __construct
Not _construct

Extremely obvious once you figure it out, but it can be sooo frustrating until you do.

I spent quite a bit of needless time debugging working code.

I even thought about it a few times, thinking it looked a little long in the examples, but at the time that just seemed silly(always thinking "oh somebody would have made that clear if it weren't just a regular underscore...")

All the manuals I looked at, all the tuturials I read, all the examples I browsed through - not once did anybody mention this!

(please don't tell me it's explained somewhere on this page and I just missed it, you'll only add to my pain.)

I hope this helps somebody else!
up
8
iwwp at outlook dot com
4 years ago
To better understand the __destrust method:

class A {
protected $id;

public function __construct($id)
{
$this->id = $id;
echo "construct {$this->id}\n";
}

public function __destruct()
{
echo "destruct {$this->id}\n";
}
}

$a = new A(1);
echo "-------------\n";
$aa = new A(2);
echo "=============\n";

The output content:

construct 1
-------------
construct 2
=============
destruct 2
destruct 1
up
3
mmulej at gmail dot com
2 years ago
*<Double post> I can't edit my previous note to elaborate on modifiers. Please excuse me.*

If both parent and child classes have a method with the same name defined, and it is called in parent's constructor, using `parent::__construct()` will call the method in the child.

<?php

class A {
public function
__construct() {
$this->method();
}
public function
method() {
echo
'A' . PHP_EOL;
}
}
class
B extends A {
public function
__construct() {
parent::__construct();
}
}
class
C extends A {
public function
__construct() {
parent::__construct();
}
public function
method() {
echo
'C' . PHP_EOL;
}
}
$b = new B; // A
$c = new C; // C

?>

In this example both A::method and C::method are public.

You may change A::method to protected, and C::method to protected or public and it will still work the same.

If however you set A::method as private, it doesn't matter whether C::method is private, protected or public. Both $b and $c will echo 'A'.
up
5
david at synatree dot com
16 years ago
When a script is in the process of die()ing, you can't count on the order in which __destruct() will be called.

For a script I have been working on, I wanted to do transparent low-level encryption of any outgoing data. To accomplish this, I used a global singleton class configured like this:

class EncryptedComms
{
private $C;
private $objs = array();
private static $_me;

public static function destroyAfter(&$obj)
{
self::getInstance()->objs[] =& $obj;
/*
Hopefully by forcing a reference to another object to exist
inside this class, the referenced object will need to be destroyed
before garbage collection can occur on this object. This will force
this object's destruct method to be fired AFTER the destructors of
all the objects referenced here.
*/
}
public function __construct($key)
{
$this->C = new SimpleCrypt($key);
ob_start(array($this,'getBuffer'));
}
public static function &getInstance($key=NULL)
{
if(!self::$_me && $key)
self::$_me = new EncryptedComms($key);
else
return self::$_me;
}

public function __destruct()
{
ob_end_flush();
}

public function getBuffer($str)
{
return $this->C->encrypt($str);
}

}

In this example, I tried to register other objects to always be destroyed just before this object. Like this:

class A
{

public function __construct()
{
EncryptedComms::destroyAfter($this);
}
}

One would think that the references to the objects contained in the singleton would be destroyed first, but this is not the case. In fact, this won't work even if you reverse the paradigm and store a reference to EncryptedComms in every object you'd like to be destroyed before it.

In short, when a script die()s, there doesn't seem to be any way to predict the order in which the destructors will fire.
up
8
prieler at abm dot at
17 years ago
i have written a quick example about the order of destructors and shutdown functions in php 5.2.1:

<?php
class destruction {
var
$name;

function
destruction($name) {
$this->name = $name;
register_shutdown_function(array(&$this, "shutdown"));
}

function
shutdown() {
echo
'shutdown: '.$this->name."\n";
}

function
__destruct() {
echo
'destruct: '.$this->name."\n";
}
}

$a = new destruction('a: global 1');

function
test() {
$b = new destruction('b: func 1');
$c = new destruction('c: func 2');
}
test();

$d = new destruction('d: global 2');

?>

this will output:
shutdown: a: global 1
shutdown: b: func 1
shutdown: c: func 2
shutdown: d: global 2
destruct: b: func 1
destruct: c: func 2
destruct: d: global 2
destruct: a: global 1

conclusions:
destructors are always called on script end.
destructors are called in order of their "context": first functions, then global objects
objects in function context are deleted in order as they are set (older objects first).
objects in global context are deleted in reverse order (older objects last)

shutdown functions are called before the destructors.
shutdown functions are called in there "register" order. ;)

regards, J
up
8
Per Persson
12 years ago
As of PHP 5.3.10 destructors are not run on shutdown caused by fatal errors.

For example:
<?php
class Logger
{
protected
$rows = array();

public function
__destruct()
{
$this->save();
}

public function
log($row)
{
$this->rows[] = $row;
}

public function
save()
{
echo
'<ul>';
foreach (
$this->rows as $row)
{
echo
'<li>', $row, '</li>';
}
echo
'</ul>';
}
}

$logger = new Logger;
$logger->log('Before');

$nonset->foo();

$logger->log('After');
?>

Without the $nonset->foo(); line, Before and After will both be printed, but with the line neither will be printed.

One can however register the destructor or another method as a shutdown function:
<?php
class Logger
{
protected
$rows = array();

public function
__construct()
{
register_shutdown_function(array($this, '__destruct'));
}

public function
__destruct()
{
$this->save();
}

public function
log($row)
{
$this->rows[] = $row;
}

public function
save()
{
echo
'<ul>';
foreach (
$this->rows as $row)
{
echo
'<li>', $row, '</li>';
}
echo
'</ul>';
}
}

$logger = new Logger;
$logger->log('Before');

$nonset->foo();

$logger->log('After');
?>
Now Before will be printed, but not After, so you can see that a shutdown occurred after Before.