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

.NET開発者のためのPHPアプリお手軽開発入門:VS Codeでコードを書きながら、PHPのクラスと名前空間を概観しよう (2/3)

[かわさきしんじ,Insider.NET編集部]

クラスの継承

 クラスを継承するにはextendsキーワードを使用する。以下に、クラス継承を行うコード例を示す。ここでは以下の内容をinherit.phpファイルに記述している(先ほどのclass.phpファイルと併せて、次ページでこのファイルのクラス定義部分を再利用してみよう)。

<?php
class Foo
{
  private $m;
  private static $cls = "Foo";

  public function __construct($m = "Foo")
  {
    $this->m = $m;
  }

  public function Hello($msg = "Hello")
  {
    echo $msg . " " . $this->m . PHP_EOL;
  }

  public static function StaticHello()
  {
    echo "Hello from class " . self::$cls . PHP_EOL;
  }
}

class Bar extends Foo
{
  private $n;
  protected static $cls = "Bar";

  public function __construct($m = "Foo", $n = "Bar")
  {
    parent::__construct($m);
    $this->n = $n;
  }

  public function Hello($msg)
  {
    parent::Hello($msg);
    echo $msg . " " . $this->n . PHP_EOL;
  }

}

class Baz extends Foo
{
  protected static $cls = "Baz";
}

$f = new Foo();
$b = new Bar();
$bz = new Baz();
$f->Hello();
$b->Hello("Goodbye");
$bz->Hello();

Foo::StaticHello();
Bar::StaticHello();
Baz::StaticHello();


クラス継承の例(inherit.phpファイル)

 ここでは3つのクラス(Foo、Bar、Baz)を定義している。クラスFooが継承元のクラスとなり、クラスBarとBazはFooを継承している。

 クラスFooでは以下を定義している。

  • privateなプロパティを2つ。うち1つは、静的プロパティ($cls)
  • 2つのメソッドと1つのコンストラクタ。うち1つのメソッドは静的メソッド(StaticHello)

 クラスBarはクラスFooを継承し、以下を定義している。

  • privateなプロパティを1つ($n)
  • protectedなプロパティを1つ($cls)
  • メソッドを1つとコンストラクタを1つ。メソッド名はクラスFooと同じ「Hello」

 クラスBazもクラスFooを継承し、以下を定義している。クラスBarとBazで$clsプロパティがprotectedになっている理由については後述する。

  • protectedなプロパティを1つ($cls)

 注目してほしいのは、クラスBarのコンストラクタだ。

class Bar extends Foo
{
  private $n;
  protected static $cls = "Bar";

  public function __construct($m = "Foo", $n = "Bar")
  {
    parent::__construct($m);
    $this->n = $n;
  }

  // …… 省略 ……

}


親クラスのコンストラクタは明示的に呼び出す必要がある(parent::__constuct())

 このコードの実行結果も示しておく。

クラス継承のサンプルコードを実行したところ クラス継承のサンプルコードを実行したところ

 PHPのドキュメント「コンストラクタ」によると、子クラスでコンストラクタを定義している場合、親クラスのコンストラクタが暗黙的に呼び出されることはない。クラスBarは独自のプロパティを持ち、その初期化のためにコンストラクタを定義しているので、上で行っているように「parent::__construct(……)」のようにして、継承元のクラスのコンストラクタを呼び出す必要がある。これに対して、子クラスにコンストラクタがない場合には、親クラスのコンストラクタが継承される。よって、クラスBazは、クラスFooを継承しているが、コンストラクタを定義していないので、クラスFooのコンストラクタを受け継ぐことになる。

 C#では、子クラス(派生クラス)のインスタンス生成時に親クラス(基底クラス)の無引数コンストラクタが自動的に呼び出されるが、PHPではそうしたことはしてくれないので注意しよう。「parent::__construct(……)」呼び出しは、C#における「: base(……)」をコンストラクタ定義に付加するものと同等だと考えておくとよいだろう。

 クラスBarではクラスFooで定義されているのと同名のメソッドHelloも定義しているが、上と同様に親クラスのメソッドを呼び出すのに「parent::Hello(……)」としている点にも注意しよう。

 実際にコードを実行すると、上の画像にも示したように、Foo/Bar/Bazの各クラスのインスタンスに対して、Helloメソッドが呼び出され、次のような出力が得られる。

Hello Foo ←$f.Hello()呼び出しの結果
Goodbye Foo ←$b.Hello("Goodbye")呼び出しの結果(クラスFooのHelloメソッド)
Goodbye Bar ←$b.Hello("Goodbye")呼び出しの結果
Hello Foo ←$bz.Hello()呼び出しの結果


各クラスのインスタンスに対するHelloメソッド呼び出しの結果

 クラスBazのHelloメソッドでは「parent::Hello(……)」呼び出しにより、クラスFooのHelloメソッドも実行されていることが分かる。一方で、クラスBazではコンストラクタを親クラスのFooから受け継いでいるため、$mプロパティの初期化が行われ、Helloメソッド呼び出しでその値の"Foo"が表示されていることも分かるはずだ。

 ちなみにクラスBarのコンストラクタで「parent::__constuct(……)」行をコメントアウトすると、実行結果は次のようになる。

Hello Foo
Goodbye ← クラスFooのコンストラクタが呼び出されず、$mプロパティが初期化されていない
Goodbye Bar
Hello Foo


「parent::__constuct(……)」行をコメントアウトした場合

 これは親クラスのコンストラクタが呼び出されず、$mプロパティが初期化されなかったために起きている。

 クラスFooで定義している静的メソッドStaticHelloは次のようなコードとなっている。これは静的プロパティの$clsを表示するだけのメソッドだ。

public static function StaticHello()
{
  echo "Hello from class " . self::$cls . PHP_EOL;
}


静的メソッドStaticHello

 ここでは「self::$cls」として、静的プロパティにアクセスしている。これが実際にどのように動作するのかを見てみよう。上の画像に示したCode Runner拡張機能で上のコードを実行した結果から関係あるところを抜き出してみよう。

Hello from class Foo ←Foo::StaticHello()呼び出しの結果
Hello from class Foo ←Bar::StaticHello()呼び出しの結果
Hello from class Foo ←Baz::StaticHello()呼び出しの結果


全てのクラスで「self::$cls」は「Foo」となる

 この場合の「self」は「StaticHelloメソッドを定義しているクラス」を参照するので、Fooとなる。当たり前といえば当たり前だが、これがBarやBazになってくれた方が都合が良いこともある。これを解決するのがスコープ定義演算子(::)の前に前置きする「static」キーワードだ。これは「メソッドを最初に呼び出したクラス」を参照することを意味する。つまり、上のコード中の「self::$cls」を「static::$cls」とすることで、「Bar::StaticHello」としたときには、Barクラスで定義されている静的プロパティの$clsが参照されるようになるということだ(Bazクラスでも同様)。そのようにコードを修正して、上のコードを実行したところを以下に示す。

「self::$cls」を「static::$cls」に変更し、実行した結果 「self::$cls」を「static::$cls」に変更し、実行した結果

 「static::$cls」により、各クラスで定義されている$clsプロパティにアクセスできていることが分かるはずだ。ただし、このときにはクラスFooのStaticHelloメソッドからクラスBar/Bazの$clsプロパティにアクセスできなければならない。そのため、これら2つのクラスでは$clsプロパティのアクセス権がprotectedになっていたわけだ(前述した通り、protectedなメンバには、そのクラスと子クラスと親クラスがアクセス可能)。

Copyright© Digital Advantage Corp. All Rights Reserved.

RSSについて

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

メールマガジン登録

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