PHPではextendsでクラスがほかのクラスを継承します。class Dog extends Animalは、DogにAnimalのすべてのpublicとprotectedのプロパティとメソッドを与えます。子は新しいメソッドを加えたり、同じ名前のメソッドを宣言して受け継いだメソッドをオーバーライドしたり、parent::method()で元のものを呼んだりできます。
DogにはコンストラクタがないのでAnimalのものを使い、だからnew Dog('Rex')が動きます。子クラスが継承できる親は1つだけで、PHPにはクラスの多重継承はありません。
parent::で親のメソッドを呼ぶ
オーバーライドは親のメソッドを完全に置き換えます。置き換えるのではなく拡張するには、子からparent::method()を呼び、その結果をもとに組み立てます。
コンストラクタでもparent::__construct(...)で同じことができます。書き忘れたときに何が起きるかも含めて、コンストラクタのページで扱っています。
子クラスでのprotectedとprivateの違い
protectedのメンバーは外のコードからは隠されていますが、ファミリーのすべてのクラスからは見えます。privateのメンバーは宣言したクラスからしか見えないので、子クラスからはまったく見えません。
子は親のprivateな$pinにアクセスできないので、子の中ではisset($this->pin)はfalseですが、親自身のメソッドhasPin()は今でもそれを読めます。サブクラスが土台にするものにはprotectedを、自由に変更できるようにしておきたい細部にはprivateを使いましょう。
ポリモーフィズムとinstanceof
親の型に対して書いたコードは、すべての子で動きます。各オブジェクトはオーバーライドしたメソッドの自分の版を実行するので、1つのループですべてを扱えます。instanceofは、オブジェクトがあるクラスかその子のどれかであるかを確認します。
親を単独で使うべきでないとき(面積0の素のShapeにはあまり意味がありません)は、親を抽象クラスにしてarea()をabstractとして宣言します。
オーバーライドのルール:シグネチャの互換性を保つ
オーバーライドするメソッドは、少なくとも親が受け付けていたものを受け付け、親の呼び出し側が使えるものを返す必要があります。任意の引数を加える、引数の型を広げる、戻り値の型を狭めることはできますが、必須の引数を加えたり、publicなメソッドをprivateにしたりはできません。
string $sep = ','を必須のstring $sepに変えると、PHPは致命的エラー(「Declaration of CsvExporter::export(array sep): string must be compatible with Exporter::export(array $rows): string」)でクラスのコンパイルを拒否します。
finalのクラスとメソッド
finalはそこで継承を止めます。finalのメソッドはオーバーライドできず、final classは継承できません。どちらもファイルのコンパイル時にチェックされるので、スクリプトはまったく開始されません。
<?php
class Payment
{
final public function charge(int $cents): string
{
return "charged $cents";
}
}
class FreePayment extends Payment
{
public function charge(int $cents): string
{
return 'free';
}
}
PHP Fatal error: Cannot override final method Payment::charge() in /home/index.php on line 12
finalクラスの場合のメッセージはClass B cannot extend final class Aです。何よりも先に権限を確認するメソッドのように、親がそのメソッドの決まった1つの動きに頼っているときは、メソッドをfinalにしましょう。
privateメソッドの罠
privateなメソッドは継承されないので、同じ名前の子のメソッドはそれをオーバーライドしません。親の中のコードは自分のprivateな版を呼び続けるので、子のメソッドが実行されると期待している人は驚きます。
greet()は今でもHelloと出力します。親の$this->word()は親のprivateなメソッドに解決されるからです。Greeterのprivateをprotectedに変えてもう一度実行すると、今度は子のメソッドが本当のオーバーライドになり、greet()はBonjourと言います。
よくある質問
PHPのextendsはどう動きますか?
class Dog extends AnimalはDogをAnimalの子にします。DogのオブジェクトはAnimalのすべてのpublicとprotectedのプロパティとメソッドを持ち、独自のものを加えられ、同じ名前のメソッドを宣言すれば受け継いだメソッドを置き換える(オーバーライドする)ことができます。
PHPで親のメソッドを呼ぶには?
子のメソッドの中でparent::methodName()を使います。たとえばreturn parent::describe() . ' and more';です。コンストラクタならparent::__construct(...)です。
PHPは多重継承をサポートしていますか?
していません。クラスが継承できる親クラスは1つだけです。複数の出どころから振る舞いを共有するには、複数のインターフェースを実装し(class A implements B, C)、トレイトを使います(use Loggable, Timestamps;)。
PHPのfinalはどういう意味ですか?
finalのメソッドは子クラスでオーバーライドできず、final classはまったく継承できません。どちらのルールに違反しても、クラスのコンパイル時に致命的エラーになります。たとえばCannot override final method A::f()です。
PHPの子クラスは親のprivateなプロパティにアクセスできますか?
できません。privateのメンバーは宣言したクラスだけのものなので、子の中では$this->pinは未定義のプロパティで(警告のあとnull)、同じ名前の子のメソッドがprivateなメソッドをオーバーライドすることもありません。子クラスが必要とするメンバーはprotectedで宣言しましょう。