ホームページ制作 オフィスオバタ

PHPで親子関係のクラスで__construct()を両方動作させる

PHPなどプログラム言語には「クラス」と呼ばれる記述方法があります。
使い方はプログラマーによりそれぞれ異なるかと思いますが、私は主にグローバル変数の重複を避けるために使用します。

どういうことかというと、プログラム制作時にグローバル変数を使うことはあります。
例えば、設定値を配列にしておくなどです。

この際に、別の部分と同じグローバル変数名を使用すると、不具合の原因になります。
グローバル変数名に入っているはずの値が、異なる内容であれば、正常動作するとは言えません。

そこでクラスを使用して、強制的にグローバル変数名が被らないようにします。
クラスならば、クラス名さえ重複しなければ、クラス内の変数名は、他のクラスの変数名と同じでも重複扱いとはならないためです。

前置きが長くなりましたがPHPのクラスで、宣言したタイミングで自動実行される関数があります。
それが__construct()関数です。

この__construct()関数ですが、一つ欠点があります。
それはクラス継承している場合に、親クラスまたは子クラスのどちらか1つしか呼び出しされないという点です。

私は継承元の親クラスの__construct()関数でクラスの初期設定を記述しています。
ですが、子クラスでも__construct()を使用すると、親クラスの__construct()がコールされなくなります。

これを何とかする方法がないか調べてみたところ、親クラス、子クラス両方の__construct()を呼び出す方法がありました。

忘備録として記録いたします。

今回の現象

今回の現象は、クラス継承した際に親クラスの__construct()がコールされないというものです。
以下のソースで現象が発生します。

<?php

$child = new CHILD_CLASS();

class PARENT_CLASS
{
	public function __construct()
	{
		echo "Oya親クラスのコントクラスタです";
	}
}

class CHILD_CLASS extends PARENT_CLASS
{
	public function __construct()
	{
		echo "Ko子クラスのコントクラスタです";
	}
}
?>

このソースを実行すると、「子クラスのコントクラスタ」と表示されます。
親クラスの__construct()はコールされませんでした。

原因は関数名のオーバーライド現象

クラス継承している場合、親クラスの関数名と子クラスの関数名が同じ場合に関数を呼び出すと、オーバーライドと呼ばれる上書きが発生します。

まずは親クラスの関数を呼び出そうとしますが、子クラスの同じ名称の関数を後からコールするため、事実上親クラスの関数が、同じ名前の子クラスに上書きされる現象です。

ある意味、クラスは独立性が高いために起こる現象ですが、今回の場合は少し具合が悪いです。
なぜならば、親クラスの初期化処理を担っている__construct()が呼び出されないからです。

これを子クラスの__construct()も親クラスの__construct()もどちらもコールされるようにできないかと思いました。

調べてみると、ちゃんとやり方がありました。

親クラスの__construct()もコールする方法

子クラス、親クラス両方の__construct()をコールするやり化は、以下の記述でできます。

class CHILD_CLASS extends PARENT_CLASS
{
	public function __construct()
	{
		parent::__construct();
		echo "Ko子クラスのコントクラストです";
	}
}

parent::__construct();と記述すると、親クラスの__construct();をコールすることになります。
こうすると、オーバーライドされずに親クラスの__construct()がコールされます。

実際にやってみると、親クラス、子クラスの__construct();がコールされました。

疑問:孫クラスで継承した場合どうなるのか?

親子関係のクラスで、子クラスの__construct()から親クラスの__construct()をコールできることがわかりました。では親、子、孫と三階層になっているクラスですべての__construct()を呼び出すことができるのか?

疑問に思ったので試してみました。

ソースはこちら

実験のためのソースはこちらです。

<?php

$mago = new MAGO_CLASS();

class PARENT_CLASS
{
	public function __construct()
	{
		echo "Oya親クラスのコントクラストです";
	}
}

class CHILD_CLASS extends PARENT_CLASS
{
	public function __construct()
	{
		parent::__construct();
		echo "Ko子クラスのコントクラストです";
	}

}

class MAGO_CLASS extends CHILD_CLASS
{
	public function __construct()
	{
		parent::__construct();
		echo "Oya孫クラスのコントクラストです";
	}

}


?>

子クラスが親クラスを継承しています。
孫クラスが子クラスを継承しています。
孫クラスは間接的に親クラスを継承しています。

子クラスでは、__construct()で、親クラスの__construct()を呼び出しています。
孫クラスでは、__construct()で、子クラスの__construct()を呼び出しています。

理屈では、孫クラスの__construct()を呼び出せば、親、子、孫の__construct()を呼び出せるはずです。

実際に試してみると

実際に試してみたところ、次のように表示されました。
「親クラスのコントクラストです子クラスのコントクラストです孫クラスのコントクラストです」

見事に、親、子、孫の__construct()が呼び出しされました。

どうやらクラス継承の階層が深くなってもそれなりに動作するようです。

注意点:次の場合は文法エラーになりました。

これならば、__construct()関数に、無条件で「parent::__construct();」を記述しておけばよいのではないか?
と思いました。

というわけで次のようなソースで実験してみました。

<?php

$child = new MAGO_CLASS();


class PARENT_CLASS
{
}

class CHILD_CLASS extends PARENT_CLASS
{
}

class MAGO_CLASS extends CHILD_CLASS
{
	public function __construct()
	{
		parent::__construct();
		echo "Mago孫クラスのコントクラストです";
	}

}


?>

孫クラスにだけ__construct()がある場合に、parent::__construct();を記述してみました。

結果は、文法エラーになりました。
そりゃそうです。親クラスに存在しない関数をコールしたら、エラーです。

__construct()に無条件で「parent::__construct();」を記述する作戦は、必ずしも正解ではないということがわかりました。

少なくとも、親クラスになりえるクラスには、「parent::__construct();」を記述してはいけないとわかりました。

ただ、親クラスには内容が空でも__construct()を記述しておいたほうがよいとも思いました。

そうすれば、クラス継承したクラスの__construct()には、「parent::__construct();」を記述してもエラーにならなくなります。

プログラミングにはルールを決めておくことが大事

プログラムは文法エラーにならなければ動きます。
ですが、バグが潜む可能性があります。

バグを生まないようにするためには、プログラミングのルールを決めておくことが有効です。
特に複数人でプログラミングする場合はなおさらです。

プログラミングは、基本作り手の独壇場です。
作っているときは何も気にせず作ります。

ですが、何年かして改修作業をする場合に気付くのです。
「あれ、これなにやってるんだ?」

自分で作っておきながら、何しているのかよくわからなくなっているケースがあります。
そんな時に、ルールを決めてプログラミングしておけば、ルールから何をやっているのかたどりやすくなります。

ローカルルールって思ったより役に立つのです。

モバイルバージョンを終了