連載
» 2018年05月10日 05時00分 公開

Web業界で働くためのPHP入門(17):PHPにおける継承とextends、オーバーライドとparent、final、protected (2/3)

[齊藤新三(著)/山田祥寛(監修),WINGSプロジェクト]

オーバーライドとparent

 継承の基本が理解できたところで応用していきます。

親クラスのメソッドを上書きするオーバーライド

 継承では、親クラスのメソッドを上書きすることができます。どういった処理なのかをサンプルで見ていきましょう。

 まず、親クラスに当たる以下のGoodsクラスを作成してください。

<?php
class Goods
{
    //商品名プロパティ
    private $name = "";
    //商品価格プロパティ
    private $price = 0;
    //コンストラクタ。商品名と商品価格を設定する
    public function __construct(string $name, int $price)
    {
        $this->name = $name;
        $this->price = $price;
    }
    //商品名と価格を表示するメソッド
    public function printPrice(): void
    {
        print($this->name."の価格: ¥".$this->price."<br>");
    }
    //商品名のゲッタ
    public function getName(): string
    {
        return $this->name;
    }
    //商品価格のゲッタ
    public function getPrice(): int
    {
        return $this->price;
    }
}
リスト4 phplesson/chap17/Goods.php

 コメントにある通り、商品名と価格のプロパティ、それらに値を設定するコンストラクタとそれぞれのゲッタ、さらに、商品名と価格を表示するメソッドから構成されたクラスです。

 次に、このクラスを継承した子クラスとして、以下のGoodsWithTaxクラスを作成してください。

<?php
class GoodsWithTax extends Goods
{
    //商品名と価格を表示するメソッド。税込みで表示するように変更
    public function printPrice(): void
    {
        //商品価格の税込み価格を計算し、表示
        $priceWithTax = round($this->getPrice() * 1.08);  // (1)
        print($this->getName()."の税込み価格: ¥".$priceWithTax."<br>");  // (2)
    }
}
リスト5 phplesson/chap17/GoodsWithTax.php

 メソッドが1つだけ定義されたクラスです。メソッド内の処理は、親クラスで定義されたゲッタを使って商品価格を取得し、それを1.08倍することで税込み価格を計算しています。その際、round()関数を使って丸め処理も行っています(1)。さらに、その税込み価格を表示しています(2)。

 ただ、そのメソッドをよく見ると、親クラスであるGoodsにも同名のprintPrice()メソッドが存在します。これは、親クラスのprintPrice()メソッドを上書きしていることになります。このように親クラスの同名メソッドを子クラスで定義し直すことを「オーバーライド」といいます(図2)。

図2 オーバーライド

オーバーライドされたメソッドの挙動

 オーバーライドされたメソッドを呼び出すとどのような挙動になるのでしょうか。リスト4のGoodsクラスとリスト5のGoodsWithTaxクラスを利用するサンプルで見ていくことにしましょう。以下のuseGoods.phpを作成し、実行してください。

<?php
require_once("Goods.php");
require_once("GoodsWithTax.php");
 
$goods = new Goods("ハンドクリーム", 350); // (1)
$goods->printPrice();  // (2)
 
$goodsWithTax = new GoodsWithTax("日焼け止め", 500); // (3)
$goodsWithTax->printPrice();  // (4)
リスト6 phplesson/chap17/useGoods.php

 実行結果は以下の通りです。

ハンドクリームの価格: ¥350
日焼け止めの価格: ¥540

 リスト6では、まず親クラスであるGoodsと子クラスであるGoodsWithTaxをそれぞれnewしています((1)と(3))。その際、プロパティにセットするデータを引数として渡しています。そして、それぞれのprintPrice()メソッドを実行しています。

 (2)ではGoodsインスタンスのprintPrice()メソッドを実行し、それが実行結果の1行目です。表示された価格が設定した価格そのままなので、GoodsクラスのprintPrice()メソッドが実行されたのが分かります。

 一方、(4)ではGoodsWithTaxインスタンスのprintPrice()メソッドを実行し、それが実行結果の2行目です。表示された価格が税込み価格なので、GoodsWithTaxクラスのprintPrice()メソッドが実行されたのが分かります。

 このように、オーバーライドされたメソッドでは、親クラスに記述されたコードは無視されて、子クラスのコードをそのまま実行することになります。

コンストラクタの扱い

 ところで、リスト6の(3)に注目してください。ここでは、new時に引数として「日焼け止め」「500」という値を渡しています。ということは、コンストラクタが実行されていることになりますが、そのコンストラクタがGoodsWithTaxクラスには記述されていません。ここで実行されているコンストラクタは親クラスであるGoodsクラスに記述されているものを使っています。このように、PHPではコンストラクタも通常のメソッドと同様に子クラスから利用できます。

 また、オーバーライドも可能です。子クラスで独自のコンストラクタを記述することで、そのコンストラクタが実行されます。

親クラスのコードも実行するparent

 では、オーバーライドされたメソッドで、親クラスのコードも実行したい場合は、方法はないのでしょうか? これは、可能です。サンプルで見ていきましょう。

 Goodsクラスを継承した以下のGoodsWithTax2クラスを作成してください。なお、ソースコードの内容は、リスト5のGoodsWithTaxクラスとほぼ同じです。違いは、クラス名と新たに追記した(1)の1行だけです。

<?php
class GoodsWithTax2 extends Goods
{
    //商品名と価格を表示するメソッド。税込みで表示するように変更
    public function printPrice(): void
    {
        //親クラスの同名メソッドの呼び出し
        parent::printPrice();  // (1)
        //商品価格の税込み価格を計算し、表示
        $priceWithTax = round($this->getPrice() * 1.08);
        print($this->getName()."の税込み価格: ¥".$priceWithTax."<br>");
    }
}
リスト7 phplesson/chap17/GoodsWithTax2.php

 ここでのポイントはリスト7の(1)で登場した「parent::」です。オーバーライドしたメソッド内で、親クラスの同名メソッドを呼び出すには、この記述を使います。構文としてまとめると以下のようになります。

構文 オーバーライドした親クラスの呼び出し

parent::同名メソッド


 実際に親クラスのメソッドが実行されるかを確かめてみましょう。このクラスを利用する以下のuseGoods2.phpを作成し、実行してください。

<?php
require_once("Goods.php");
require_once("GoodsWithTax2.php");
 
$goods = new GoodsWithTax2("リップクリーム", 200);
$goods->printPrice();  // (1)
リスト8 phplesson/chap17/useGoods2.php

 実行結果は以下の通りです。

リップクリームの価格: ¥200
リップクリームの税込み価格: ¥216

 (1)でGoodsWithTax2インスタンスのprintPrice()メソッドを実行しています。実行結果から、明らかに親クラスの同名メソッドも実行されていることが読み取れます。

親コンストラクタの呼び出し

 この「parent::」は親コンストラクタの呼び出しにも使います。子クラスでコンストラクタをオーバーライドし、その子クラスのコンストラクタ内で親クラスのコンストラクタを呼び出したい場合は、以下のように記述します。

public function __construct(……)
{
    parent::__construct();
      :
      :
}

注意!!「final」

 オーバーライドの話が出てきたところで、1つ補足しておきます。

 メソッドによっては、子クラスでオーバーライドされたくない場合というのもあり得ます。その場合は、以下のようにメソッド定義に「final」を付けます。

final public function doSomething() {……}

 同様に、継承不可のクラス、つまり子クラスが作成できないクラスを定義したい場合は、以下のように「final」をクラス宣言に付けます。

final class Base {……}

Copyright © ITmedia, Inc. All Rights Reserved.

編集部からのお知らせ

RSSについて

アイティメディアIDについて

メールマガジン登録

@ITのメールマガジンは、 もちろん、すべて無料です。ぜひメールマガジンをご購読ください。