[PHP] マジックメソッド__get()、__set()の特徴:アクセサプロパティとして使えるが問題点も

マジックメソッドとは

PHPのクラスに特別に設定されたメソッド。接頭辞(プリフィックス)としてアンダースコア2つ(__)がつけられている。

コンストラクタも、__construct()というマジックメソッドになっている。

それらを自身のクラスの中で再定義することで使う。

なぜかPHPではそれを「オーバーロード」と呼んでいる。

__get()と__set()の共通する特徴

呼び出そうとしたプロパティが宣言されていない、またはアクセスできない場合に呼ばれる

class Sample {
	private $member;
	
	public function __get(value) {
		
	}
	
	public function __set(name, value) {
	
	}
}

$sample = new Sample;
echo $sample->num;
echo $sample->member = 1;

Sample#numは宣言されていないので、__get('num’)という形で呼び出される。

一方、Sample#memberは宣言されているものの、アクセスレベルがprivateなので外部から呼び出せず、この場合も__set('member’, 1)という形になる。

注意点

問題になりやすいのは後者の場合。

アクセスレベル(アクセス指定子)によって、呼ばれる呼ばれないに変化があるので、プログラマが注意してコーディングする必要がある。

もし上の例で$memberがpublicか、Sampleクラス内部からのアクセスだった場合、__get()は呼び出されず、そのプロパティ(メンバ変数)がそのまま呼ばれる。

クラスが継承されている場合も、常にプロパティが優先される

class Super {
	public $abc = 1;

	function __construct() {
		
	}

	public function __get($name) {
		switch ($name) {
			case 'abc':
				return 3;
		}
	}
}

class Sub extends Super {
	
	public function __get($name) {
		switch ($name) {
			case 'abc':
				return 10;
		}
	}
}

$sub = new Sub;
echo $sub->abc;

この例の場合、外部から呼び出そうとしたとき、Subクラスでは$abcが宣言されていないが、スーパークラスの$abcがあるのでそちらが呼ばれる。

しかし、スーパークラスの$abcアクセスレベルがprotected以下だった場合、スーパークラスで__get()、__set()が定義(オーバーロード)されていたとしても、Subクラスの__get()、__set()が呼ばれる。

つまり、スーパークラス側の実装に依存する。

残念ながら、スーパークラスのプロパティがpublicだった場合、サブクラス側でどうあがいても、自身のクラスにおけるマジックメソッドを通すことはできない。

ということは、とりあえずプロパティをpublicで指定して、あとで処理を追加する必要が出たらマジックメソッドを使うというやり方は、スーパークラスに同名のプロパティが存在する場合は使えないことになる。

きちんとしたオブジェクト指向で継承させたい場合は、スーパークラス側もすべて__get()、__set()で統一する必要がある。そうしておけば、サブクラス側でマジックメソッドを再定義すれば、そちらが呼ばれることになる。

しかし、今度はそうすると、通常のプロパティや関数よりも処理が遅くなるデメリットがある。

まとめ

基本的に、使わないほうがいい。

C#やActionScript 3.0、JavaScriptなどにはメソッド(メンバ関数)のように処理を指定できながらプロパティ(メンバ変数)のようにアクセスできる機能がある。

C#・ActionScript 3.0では「プロパティ」、JavaScriptでは「アクセサプロパティ」と呼んでいる。

PHPでは上記のように、マジックメソッドの__get()、__set()を使って擬似的に再現する必要があるが、動作がややトリッキーなのであまり使わないほうがいいだろう。

素直にgetSample()、setSample()といったJavaライクにgetter/setterメソッドを定義することをおすすめする。