PHP に限らず、プログラミング中にライブラリ内のコードで生成されたインスタンスを扱うことは多々あります。このインスタンスの元となるクラスを継承した自前のクラスにして、取り扱うインスタンスを作りたいものにより適した形にしたいことがしばしばあります。そういった時、言語による継承を行うことは案外大変です。インスタンス生成クラスを継承した自前のクラスを作る必要があり、往々にしてインスタンス生成クラスの実装を知る必要があるからです。PHP のマジックメソッドを使うことでインスタンス生成クラスのことを知らないまま public な可視性を持つ部分を増築することができます。
使うのはマジックメソッドのオーバーロード(動的にメソッド、プロパティを生成する機能)である __call, __callStatic, __get, __set, __isset, __unset で、実装は次です。
PHP: オーバーロード – Manual
<?php class LibInstance { public $libProp = 'libProp'; public function libEcho() { echo 'lib'; } public function overrideEcho() { echo '元クラス'; } } class LibFactory { public function createLibInstance(): LibInstance { return new LibInstance(); } } /** * Class MixinInstance * @mixin LibInstance */ class MixinInstance { protected LibInstance $libInstance; /** * MixinInstance constructor. * @param LibInstance $libInstance */ public function __construct(LibInstance $libInstance) { $this->libInstance = $libInstance; } public function overrideEcho() { echo '継承っぽいクラス'; } /** * このクラスに存在しないメソッドが呼ばれたら継承元のメソッドを呼ぶ * @param $name * @param $arguments */ public function __call($name, $arguments) { $this->libInstance->$name(...$arguments); } /** * このクラスに存在しない静的メソッドが呼ばれたら継承元の静的メソッドを呼ぶ * @param $name * @param $arguments */ public static function __callStatic($name, $arguments) { LibInstance::$name(...$arguments); } /** * このクラスに存在しないプロパティが呼ばれたら継承元のプロパティを呼ぶ * @param $name * @return mixed */ public function __get($name) { return $this->libInstance->$name; } /** * このクラスに存在しないプロパティに値がセットされようとしたら継承元のプロパティに値をセットする * @param $name * @param $value */ public function __set($name, $value) { $this->libInstance->$name = $value; } /** * このクラスに存在しないプロパティがissetされようとしたら継承元のプロパティをissetする * @param $name * @return bool */ public function __isset($name): bool { return isset($this->libInstance->$name); } /** * このクラスに存在しないプロパティがunsetされようとしたら継承元のプロパティをunsetする * @param $name */ public function __unset($name) { unset($this->libInstance->$name); } } $libInstance = (new LibFactory())->createLibInstance(); $mixinInstance = new MixinInstance($libInstance); $mixinInstance->libEcho();// lib echo $mixinInstance->libProp; // 'libProp' $mixinInstance->overrideEcho();// 継承っぽいクラス
こんな感じで元インスタンス、元クラスを都度参照する様にすると public な機能を残したまま追加でメソッドやプロパティを生やしたように外から扱えます。内部実装の改造はやはり内部を知る必要がありますが、ちょっとプロパティやメソッドをまとめておきたい様な時には便利です。
ちなみにこのマジックメソッドによる継承もどきを使うと原義の多重継承のように複数クラスを継承したかのように振舞えます。一つのクラス内で好き勝手継承もどきをするとコードが追えなくなって非常に辛くなります。