pyてよn日記

一寸先は闇が人生

PHP の勉強 その3 - クラスの継承

 PHP の勉強 3 回目.前回はオブジェクト指向の基礎を学んだ.前回の内容は以下の記事にまとめてある.

pyteyon.hatenablog.com

 本記事では前回に続きオブジェクト指向について学んでいく(内容はページ先頭の目次を参照).

前回の復習

オブジェクト指向の目的

  • インターフェースの共通化
  • プログラムの整理,構造化,再利用可能化

オブジェクト指向の基礎

  • クラス:オブジェクトの設計図.class ClassName {} で定義.
  • インスタンス:クラスを実体化したもの.$ins = new ClassName();インスタンス生成.
  • プロパティ:インスタンスが持つデータ.
  • メソッド:インスタンスに関連する処理.インスタンスが持つ関数.public function hoge(){} と書くことで定義できる.
  • $this:クラス定義内でインスタンス自身にアクセスする方法.クラス定義内で $this->method など.
  • コンスラクタ:インスタンス生成時に実行されるメソッド.クラス定義内で public function __construct() {} と 書くことで定義できる.
  • -> 演算子でプロパティ,メソッドへアクセスできる.例えば,$ins->name
  • カプセル化:クラスのプロパティ,メソッドへのアクセスの制限.publicprivate で設定.
  • コードの分割,外部 PHP ファイルの読み込み:require_once('hoge.php') で他の PHP ファイルを読み込める.

クラスプロパティ,クラスメソッド

 クラス(設計図)とインスタンス(クラスを実体化したもの)の区別を意識する.

クラスプロパティ

 個々のインスタンスが持つデータのことをプロパティと呼ぶが,クラスがもつデータのことをクラスプロパティと呼ぶ.プロパティが個々のインスタンスのデータを表すのに対し,クラスプロパティはクラス全体に関するデータ(例えば,あるクラスからいくつのインスタンスが生成されたか,など)として用いられる.

 クラスプロパティを定義するには,static というキーワードを用いて,

public static $propertyName;

と記述する(一般的には private で定義する).クラス定義の外部でクラスプロパティにアクセスするには,

クラス名::$クラスプロパティ名

と記述する.このとき $ をクラスプロパティ名の先頭に付けるため注意する.通常のプロパティと異なり,クラスプロパティは -> 演算子でアクセスできない.

<?php
class Person {
    // class property
    public static $count = 0;

    // property
    private $name;

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

echo MyClass::$count;
// 0

?>

クラスメソッド

self:クラス定義内でクラス自身を表す

 クラス定義内でクラスプロパティにアクセスするには self という特殊な変数を用いる.self はクラス定義内に記述するとそのクラス自身のこと表す

self::$クラスプロパティ名

とクラス定義内に記述することで,クラスプロパティにアクセスできる.

 例えば,「生成されたインスタンスの数」をクラスプロパティで管理したい場合,クラスプロパティの初期値を 0 として,コンストラクタ内でそのクラスプロパティをインクリメントすれば良い.以下に例を示す.

<?php
class Person {
    // class property
    public static $count = 0;

    // property
    private $name;

    // constructor
    public function __construct($name) {
        // set property
        $this->name = $name;

        // increment class property
        self::$count++;
    }
}

$per1 = new Person('Bob');
echo Person::$count;
// 1

$per2 = new Person('Alice');
echo Person::$count;
// 2

?>

クラスメソッド

 一般的に,クラスプロパティは privateカプセル化しておき,クラスプロパティのためのゲッターを定義する.個々のインスタンスに関係のない,クラスプロパティを処理するためのメソッドクラスメソッドという.クラスメソッドは,static を用いて,クラス定義内に以下のように記述することで定義できる.

<?php
// クラス定義内
public static function classMethod() {
    // 処理
}

?>

 クラス定義の外部で呼び出すときは,

クラス名::クラスメソッド名

と記述し,このとき,クラスプロパティへのアクセスと異なりクラスメソッド名の先頭に $ は必要ない.一旦確認すると,クラスの外部から

  • クラスプロパティへアクセス:MyClass::$classProperty
  • クラスメソッドへアクセス:MyClass::classMethod

となる.クラスプロパティ,クラスメソッドはそれぞれのインスタンスに属するプロパティ,メソッドと異なり,-> 演算子ではアクセスできないことに注意.

 先程の Person クラスのクラスプロパティ $count のゲッターを書き加えると以下のようになる.

<?php
class Person {
    // class property
    private static $count = 0;

    // property
    private $name;

    // constructor
    public function __construct($name) {
        // set property
        $this->name = $name;

        // getter: increment "class property"
        self::$count++;
    }

    // class method(クラスプロパティのゲッター)
    public static function getCount() {
        return self::$count;
    }
}

$per1 = new Person('Bob');
// echo Person::$count; private だからエラー
echo Person::getCount();
// 1

$per2 = new Person('Alice');
// echo Person::$count; private だからエラー
echo Person::getCount();
// 2

?>

クラスの継承

継承とは?

 既に定義されているクラスのプロパティやメソッドを,新しく定義するクラスに引き継ぐことを「継承」という.引き継がれるクラスを親クラス,継承してできる新しいクラスを子クラスという.子クラスは,親クラスのプロパティやメソッドを全て引き継いだ上で独自の機能を追加することができる.

f:id:pytwbf201830:20190609034732p:plain
クラスの継承

PHP におけるクラスの継承の書き方

 継承を用いて新しく子クラスを定義するときは,「extends」を用いて class 子クラス名 extends 親クラス名 のと記述する.Parent という親クラスを,Child という子クラスに継承する場合,以下のようになる.

親クラス

<?php
class Parent {
    // code
}

?>

子クラス

<?php
class Child extends Parent {
    // code
}

?>

継承の使い方

 子クラスは親クラスのプロパティやメソッドを引き継いでいるため,親クラスで定義されているメソッドを呼び出せる.

<?php
class Parent {
    // property
    private $name;

    // constructor
    public function __construct($name, ...) {
        $this->name = $name;
    }

    // getter
    public function getName() {
        return $this->name;
    }
}

?>

子クラス

<?php
// Parent クラスを継承
class Child extends Parent {
    // 子クラス独自のメソッドを書く
}

// 親クラスのコンストラクタが実行される
$alice = new Child("Alice", ...);

// 親クラスのメソッドの呼び出し
echo $alice->getName();
// Alice

?>

独自メソッド,プロパティ

 子クラスでは,親クラスの性質を全て引き継ぎつつ,独自のプロパティ,メソッドを定義することができる.子クラスで定義したメソッドやプロパティは親クラスからは呼び出せないため注意(引き継ぐだけで方向は一方向).

 子クラスにおけるメソッドの呼び出しには規則がある.子クラスでメソッドを呼び出すと,

  1. 子クラスで定義されているメソッドを検索
  2. 1 で見つからない場合,継承した親クラスでそのメソッドが定義されているか検索

というようにメソッドか検索され,呼び出される.

instanceof:クラスによって処理を分ける

 あるインスタンスPHP 変数)が特定のクラスからのインスタンスかどうかを判定する型演算子として,instanceof がある.以下のように記述し,判定結果は bool 型(true or false)で返される.

<?php
if ($ins instanceof MyClass) {
    // もし $ins が MyClass のインスタンスであれば処理を実行
} else {
    // code
}

?>

instanceof の使い方

 instanceof の実例を見てみる.以下では,インスタンス $person が Person クラス,NotPerson クラスから生成されたものかどうかを判定している.

<?php
class Person {}
class NotPerson {}

$person = new Person();
if ($person instanceof Person) {
    // Person から生成されたインスタンスかどうかを判定
    echo '$person は Person のインスタンスです';
}

if ($person instanceof NotPerson) {
    echo '$person は NotPerson のインスタンスです';
} else {
    echo '$person は NotPerson のインスタンスではない';
}

?>

 また,あるインスタンス特定の親クラスを継承したクラスのインスタンスであるかどうかも調べることができる.

<?php
class Parent {}
class Child extends Parent {}  // Parent を継承

// 子クラスのインスタンス生成
$ins = new Child();

if ($ins instanceof Child) {
    echo '$ins は Child のインスタンスです';
}

if ($ins instanceof Parent) {
    echo '$ins は Parent のインスタンスです';
}

?>

 もし特定のクラスではないインスタンスを判定したいときは,否定演算子 ! を用いて以下のように記述すれば良い.

<?php
if (!($ins instanceof MyClass)) {
    // $ins が MyClass のインスタンスではないときに
    // 処理が実行される.
}

?>

まとめると,'instanceof' 演算子によって,

を判定することができる.

protected:親クラス,子クラスの定義内からプロパティへのアクセスを可能にする

オーバーライド

 子クラス独自のメソッドを実装する方法は先述した.次に,子クラス独自のコンストラクタを定義する方法を学ぶ.

 親クラスにおいてコンストラク__construct() が定義されている場合,継承すると子クラスのインスタンスが生成されたときは親クラスのコンストラクタが実行される.しかし,クラスの継承において,同じ名前のメソッドを子クラスで定義するとメソッドの中身を上書きすることができる.子クラスにおけるメソッドの上書きを「オーバライド」という.

 オーバーライドは,コンストラクタに限らず他のメソッドでも同様に行うことができる.

 オーバーライドの仕組みは単純で,子クラスにおけるメソッドの呼び出し順序に習っているだけである.

  1. 子クラスで定義されているメソッドを検索(子クラスの __constructor() を検索)
  2. 1 で見つからない場合,継承した親クラスでそのメソッドが定義されているか検索(親クラスの __constructor() を検索)

子クラスとアクセス権

 プロパティのアクセス権が private の場合,クラスの定義外(インスタンス)から -> 演算子を用いて直接アクセスすることができない.

 実は private なプロパティには,子クラスからもアクセスできない.そのため,子クラス定義内でコンストラクタのオーバーライドを行う際には,子クラスのコンストラクタから親クラスの private なプロパティへはアクセスできない.

<?php
class Parent {
    private $name;
}

class Child extends Parent {
    // コンストラクタのオーバーライド
    public function __construct($name, ...) {
        $this->name = $name;
        // ERROR:親クラスでアクセス権を private に
        // しているためアクセスできない.
    }
}

?>

protected:親クラス,子クラスの定義内からプロパティへのアクセスを可能にする

 子クラスから親クラスのプロパティへアクセスするには,親クラスの定義内においてプロパティのアクセス権を private から protected に変える必要がある.

<?php
class Parent {
    // アクセス権を protected へ変更
    // private $name;
    protected $name;
}

class Child extends Parent {
    // コンストラクタのオーバーライド
    public function __construct($name, ...) {
        $this->name = $name;
        // 親クラスでアクセス権を protected に
        // しているためアクセスできる
    }
}

?>

 アクセス権を protected に設定することにより,

  • protected のプロパティを定義したクラスの中(親クラス定義内
  • protected のプロパティを定義したクラスを継承した子クラスの中(子クラス定義内

の 2 つの定義内からのみプロパティへアクセスできるようになる.

アクセス権まとめ

 クラスのプロパティ,メソッドへのアクセス権,publicprotectedprivate をまとめると,

  • public:どこからでもアクセス可能(クラス定義内,インスタンスからアクセス可能).
  • protected:そのクラスと子クラス内からのみアクセス可能.
  • private:そのクラス内からのみアクセス可能.クラス外(インスタンス)からはアクセス不可.

 表にもまとめておく.下に行くほどアクセスできる範囲が狭くなっている.

どこからアクセスするか クラス内 子クラス内 クラス,子クラスの外
public
protected ×
private × ×

parent:親クラスのメソッドを呼び出すキーワード

 子クラスの定義内で「parent」というキーワードを使い,parent::メソッド名 と記述することで親クラスのメソッドへアクセスできる(::スコープ定義演算子).

 これを用いると,親クラスのメソッドをオーバーライドする際に,親クラスのメソッドを利用しつつ,子クラス独自の処理を簡潔に加えることができるparent::メソッド名 を書いた後に,子クラスで加えたい処理を差分として書くようなイメージである.

parent の使い方

 基本は「子クラスの定義内で parent::親クラスのメソッド名 と記述する」ことである.以下では,Person クラス(親クラス)を継承して,Teacher クラス(子クラス)を定義する.Teacher クラスでは,コンストラクタでプロパティ $subject を設定する処理を追加するために,Person クラスのコンストラクタをオーバーライドしている.

親クラス

<?php
class Person {
    // property
    protected $name;

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

    // getter
    public function getName() {
        return $this->name;
    }
}

?>

子クラス

<?php
// Parent クラスを継承
class Teacher extends Person {
    private $subject;

    // constructor
    public function __construct($name, $subject) {
        // 親クラスのメソッド(コンストラクタ)
        parent::__construct($name);

        // 子クラス独自の処理
        $this->$subject = $subject;
    }

    // getter
    public function getSubject() {
        return $this->subject;
    }
}

?>

 上記の例では実装が簡単なため parent の恩恵が分かりづらいが,もう少し複雑なメソッドをオーバーライドする際には parent の利点が分かるはずである.

ポイントまとめ

クラスメソッド,クラスプロパティ

  • クラスメソッドは個々のインスタンスではなく,クラスのデータを表す.そしてクラスプロパティを処理するメソッドのことをクラスプロパティという.これらはクラス全体の情報の管理(ソースコード中でインスタンスがいくつ生成されたかなど)に利用される.
  • public,private の後ろに static と付けることで定義できる.

クラスの継承

継承の基礎

  • 継承:既に定義されているクラスのプロパティ,メソッドを新しく定義するクラスに引き継ぐこと
  • 引き継がれるクラスを親クラス,継承してできる新しいクラスを子クラスという.
  • PHP では,extends を用いて class Child extends Parent {} と記述することでクラスの継承を行うことができる.

独自メソッド,プロパティ

  • 子クラスでは,親クラスの性質を全て引き継ぎつつ,独自のプロパティ,メソッドを定義できる.
  • 子クラスで定義したメソッドやプロパティは親クラスからは呼び出せない
  • 子クラスのインスタンスでメソッドを呼び出す場合,「子クラス -> 親クラス」,の順でメソッドが検索され,該当するメソッドが見つかれば実行される.

instanceof:クラスによって処理を分ける

 'instanceof' 演算子によって,

を判定することができる.判定結果は bool 型(true or false)で返される.

子クラスとアクセス権,protected

 クラスのプロパティ,メソッドへのアクセス権,publicprotectedprivate をまとめると,

  • public:どこからでもアクセス可能.
  • protected:そのクラスと子クラス内からのみアクセス可能.
  • private:そのクラス内からのみアクセス可能.クラス外(インスタンス)からはアクセス不可.

 表にもまとめておく.下に行くほどアクセスできる範囲が狭くなっている.

どこからアクセスするか クラス内 子クラス内 クラス,子クラスの外
public
protected ×
private × ×

parent:親クラスのメソッドを呼び出すキーワード

 子クラス定義内で parent を用い,parent::親クラスのメソッド名 と記述すると,親クラスのメソッドへアクセスできる.親クラスのメソッドを利用したり,オーバーロードにより親クラスのメソッドに処理を付け加えたりする場合に有効である.

補足:スコープ定義演算子 ::

 補足には少し勿体無いかもしれないが,スコープ定義演算子 :: に関する覚書を補足として加えておく.

 ダブルコロン :: により,static,定数及びオーバーライドされた親クラスのプロパティやメソッドにアクセスできる.このダブルコロンのことをスコープ定義演算子という.

 クラスを用いて一つの名前空間(スコープ)を定義することができるが,その定義した名前空間にどこからでもアクセスできるようにするのがスコープ定義演算子 :: の役割である.

クラス定義外からの ::

 例えば,クラス定義内で定義した定数などにクラス定義外からアクセスしたい場合を考える.以下では,クラスを用いて MyClass という名前空間を定義し,その名前空間の定数にクラス定義外からアクセスを試みている.

<?php
class MyClass {
    const CONST_VALUE = 'A constant value';
}

$classname = 'MyClass';
echo $classname::CONST_VALUE; // PHP 5.3.0 以降で対応

echo MyClass::CONST_VALUE;

?>

クラス定義内からの ::

 PHP では,クラス定義の内部で三つのキーワード selfparentstatic を用いることにより,クラス定義の内部から自分自身(子クラス),親クラスのプロパティ,メソッドにアクセスすることができる

<?php
class MyClass {
    const CONST_VALUE = 'A constant value';
}

class Child extends MyClass {
    public static $my_static = 'static var';  // クラスプロパティ
    public static function doubleColon() {
        // 親クラスの定数にアクセス
        echo parent::CONST_VALUE . '\n';
        // クラスプロパティ(static プロパティ)
        echo self::CONST_VALUE . '\n';
    }
}

$classname = 'Child';
$classname::doubleColon(); // PHP 5.3.0 以降で対応

MyClass::CONST_VALUE;

?>

子クラスにおけるメソッドのコール

 本記事では,子クラスのメソッドをコール(メソッドの呼び出し)するとき,

  1. 子クラスで定義されているメソッドを検索
  2. 1 で見つからない場合,継承した親クラスでそのメソッドが定義されているか検索

というコールをした時の検索順序があることを述べたが,厳密には以下のような説明になる.

 子クラスが,継承した親クラスのメソッドの定義をオーバーライドする際,PHP は親クラスのメソッドをコールしない.つまり,親クラスのメソッドをオーバーライドすると子クラスのメソッドが優先的にコールされる

 親クラスのメソッドをコールするかしないかは,親クラスを継承した子クラスに責任がある.これは,コンストラクタおよびデストラクタ,オーバーロード,そして マジックメソッドの定義にも適用される.

参考