なお先ほどのソースコードでは、意図的にプロパティを「public」にしました。これを「private」にすると、以下のようになります(7行目と15行目)。
<?php class hoge { public function A() { echo "call hoge's A\n"; } // private $i_ = 1; } // class foo extends hoge { public function A() { echo "call foo's A\n"; } // private $i_ = 999; } // $foo_obj = new foo(); var_dump($foo_obj);
object(foo)#1 (2) { ["i_":"foo":private]=> int(999) ["i_":"hoge":private]=> int(1) }
結果を見ると、上書きにはならず、二つの「$i_」が「同時に存在する」状態になります。これは、なぜでしょうか?
privateは「自分のクラス内でのみアクセスが可能」な、「閉じたアクセス権」です。「自分のクラスのみでアクセスが可能」なので、継承した場合は名前が同一であっても「クラスごとに別々の値を持つ」ことになり、「上書きされず、それぞれ別に値を持っている」ような形になります。
この動作を「意図的に使う」のはいささか難易度が高いのに加えてめったにやらないことですが、「誤ってこのように実装してしまう(結果としてバグを産む)」ことは“まれ”にあるので、頭の片隅にでも、この状況を入れておくとよいでしょう。
前回、protectedについての説明はほぼ省略し「privateと同じようなもの」としましたが、継承について基本を学び始めたので、このタイミングで「privateとprotectedの違い」について、併せて学んでいきましょう。
先に簡単にまとめますと「privateは書いたクラス内のみ」「protectedは書いたクラス内、もしくはその継承先のクラス」でアクセス可能になります。プロパティとメソッドについては特に違いはないので、メソッドで実験コードを書いてみましょう。
<?php class hoge { private function h_pri_func() { echo "call h_pri_func\n"; } protected function h_pro_func() { echo "call h_pro_func\n"; } } // class foo extends hoge { private function f_pri_func() { echo "call f_pri_func\n"; } public function test() { $this->f_pri_func(); //$this->h_pri_func(); // エラーになる $this->h_pro_func(); } } // $foo_obj = new foo(); //$foo_obj->h_pri_func(); // エラーになる //$foo_obj->h_pro_func(); // エラーになる //$foo_obj->f_pri_func(); // エラーになる $foo_obj->test();
private、protectedとも「クラス外からのアクセスは許可しない」ので、「$foo_obj->h_pri_func();」など18行目や25〜27行目のコードは全てエラーとなります。
最後の28行目で、class foo内のtestメソッドを使って、classの内部から、privateおよびprotectedなメソッドを呼び出しています。16〜20行目にある通り、testメソッドは「fooクラスの中」にあるので、「fooクラスのprivateまたはprotected」(コードでは13〜15行目のf_pri_funcメソッド)および「hogeクラスのprotected」は、アクセス可能な範囲です。そのため、fooクラスのtestメソッドから「protected function h_pro_func()」(7〜9行目)は呼び出しが可能です。一方で「private function h_pri_func()」は「hogeクラスに記載されているprivateなメソッド」なので、fooクラスに記述されているtestメソッドからアクセスしようとするとエラーになります(18行目)。
protectedも一般的に使われるアクセス権なので、しっかりと押さえておきましょう。
前回学んだコンストラクターとデストラクターですが、クラスを継承するときには、少しだけ注意が必要です。
まず、問題があるコードを書いて、問題点を認識してみましょう。
<?php class hoge { public function __construct() { echo "run hoge constructor\n"; } public function __destruct() { echo "run hoge destructor\n"; } } class foo extends hoge{ public function __construct() { echo "run foo constructor\n"; } public function __destruct() { echo "run foo destructor\n"; } } // $obj = new foo();
動きとしては明確なのですが、fooクラスのコンストラクター(12〜14行目)とデストラクター(15〜17行目)が、hogeクラスのコンストラクター(4〜6行目)とデストラクター(7〜9行目)を「オーバーライドしてしまった」ために、hogeクラスのコンストラクターとデストラクターが「呼ばれない」状況になります。
これが「意図的」ならよいのですが、大抵の場合コンストラクターには「自分のクラスのための初期処理」が書いてあり、デストラクターがある場合は「必要な終了処理」が書かれているために、「オーバーライドによって継承元クラスのそれらが上書きされてしまうと困る」ことの方が多いのです。
PHPでは「(オーバーライドしたときなどのために)親クラスのメソッドを呼ぶ」ために使う、「parent」という書式があります。
先ほどの「継承におけるコンストラクターとデストラクターのコード例」を、parentを使って適切にコンストラクターとデストラクターを実装し直すように修正してみましょう。
<?php class hoge { public function __construct() { echo "run hoge constructor\n"; } public function __destruct() { echo "run hoge destructor\n"; } } class foo extends hoge{ public function __construct() { parent::__construct(); // 継承元クラスのコンストラクターを呼ぶ echo "run foo constructor\n"; } public function __destruct() { echo "run foo destructor\n"; parent::__destruct(); // 継承元クラスのデストラクターを呼ぶ } } // $obj = new foo();
13行目と18行目にあるように、parentを使うことで「オーバーライドした親クラスのメソッドが呼べる」ことを覚えておきましょう。このparentは、コンストラクター/デストラクターだけでなく、あらゆるメソッドで使えます。
また、通常parentを使うときは、13行目の「__construct」のように「メソッド(上記例では12〜15行目)の先頭行で呼ぶ」のが基本ですが、「__destruct」だけは「メソッド(上記例では16〜19行目)の最後の行(18行目)で」呼ぶ方がよいでしょう。ネストの構造になるので、通常は「継承元 → 継承先」ですが、閉じるものについては「継承先 → 継承元」というふうになっていくからです。
プログラミング言語によっては(具体的にはJavaやC++など)、継承元クラスのコンストラクター(やデストラクター)は自動で呼ばれますが、PHPは自動では呼んでくれません。そのためPHPでは、parentによって「明示的に、継承元クラスのコンストラクター(やデストラクター)を呼ぶ必要がある」ことを覚えておきましょう。
Copyright © ITmedia, Inc. All Rights Reserved.