本来、クラス内のprivateメソッドはクラス内の他メソッドでしか実行できません。クラスオブジェクトの外からは実行不可能。
でも、Closureクラスを使えばそれができます。
クロージャをクラスメソッドとしてあとで追加できる方法を利用すれば。
このクロージャはクラスの外に定義するのに、クラスメソッドと同じように動きます。
隠蔽したメソッドはClosure::bind()で外から実行可能
ふつう、クラス内のprivateメソッドは、オブジェクトの外から実行することはできません。クラス内の他のメソッドからしか実行できない。
しかし、Closureクラスのbind()を使えば外からでも参照可能です。
そのためにはクロージャが必要です。
(今回はアロー関数も使用。)
まず、クロージャ、無名関数、アロー関数、Closureクラスについてよく分からない人はこちらからどうぞ。
言葉で説明するよりもコードを見せたほうが早いです。サンプルコードです。
class Test
{
private function exec()
{
return "private method return";
}
private static function sexec()
{
return "private static method return";
}
}
// クロージャで外からprivateメソッド実行
$closure1 = fn() => $this->exec();
$closure2 = static fn() => Test::sexec();
// クロージャにクラスオブジェクトをバインド(繋げる)
$new_closure1 = Closure::bind($closure1, new Test(), 'Test');
$new_closure2 = Closure::bind($closure2, null, 'Test');
var_dump($new_closure1());
var_dump($new_closure2());
string(21) "private method return"
string(28) "private static method return"
クロージャはあとから追加できるクラスメソッドになる
Closureクラスは内部で、クラスオブジェクトをバインド(連携)する機能をもってます。
それを使えば、外で定義したクロージャ(関数)からオブジェクト内部のprivateメソッドを実行できる。
静的(static)なメソッドもクロージャをstaticにして合わせれば実行可能。
staticではバインドをリセットする。
(bind()の第2パラメータをnull指定)
クラス内のstaticなものはクラスの型(クラス名)だけで十分。
(bind()の第3パラメータの文字列)
ただしコーディングチェックでは、クロージャ内のコードで『Undefined method 'sexec'』が発生します。
『privateメソッドで外から実行できないからその関数は知らんよ?』というエラー。
これは無視してOK。動作上は問題ありません。
おそらく、そこまでコーディングチェックツールが対応していないんでしょう。
このバインド機能は、クロージャはあとから追加できるクラスメソッドと思ったほうが分かりやすい。
クロージャがクラスのメソッドになるから、既存のprivateメソッドが実行できる。
PHP公式ドキュメント
Closure::bindTo()でも同じことができる
ClosureにはbindTo()というメソッドもあります。さっきのサンプルはこれでも同じことができる。
// クロージャで外からprivateメソッド実行
$closure1 = fn() => $this->exec();
$closure2 = static fn() => Test::sexec();
// クロージャにクラスオブジェクトをバインド(繋げる)
$new_closure1 = $closure1->bindTo(new Test(), 'Test');
$new_closure2 = $closure2->bindTo(null, 'Test');
var_dump($new_closure1());
var_dump($new_closure2());
string(21) "private method return"
string(28) "private static method return"
bind()では、"Closure::" のstaticメソッドのパラメータでクロージャを指定しましたが、bindTo()はクロージャオブジェクトからメソッドを呼び出すので、パラメータにクロージャ指定はありません。
オブジェクトからメソッドを呼んでるので '->' を使ってるのがポイント。
PHP公式ドキュメント
call()は半分できて半分できない
クラスオブジェクトのバインドからクロージャの実行までしてくれる call() もあります。ただし、staticなものには使えません。
bind(), bindTo()は、パラメータのスコープでクラス名を指定できるので、staticなものでもできるんですが、call()にはそれがなく、クラスオブジェクト(インスタンス)だけしか指定できないのが理由。
// クロージャで外からprivateメソッド実行
$closure1 = fn() => $this->exec();
$closure2 = static fn() => Test::sexec();
// クロージャにクラスオブジェクトをバインド(繋げる)してクロージャを実行
$test = new Test();
var_dump($closure1->call($test));
var_dump($closure2->call($test));
string(21) "private method return"
PHP Warning: Cannot bind an instance to a static closure in /home/vagrant/php-test.php on line 8
NULL
protected はどうか?
今度はprotectedなメソッドではどうなるか見てみましょう。本来protectedは、子クラスだけがpublicかのように参照できるもので、外からはprivateと変わらず参照できません。
予想としては見れると思います。
class Test
{
protected function exec()
{
return "protected method return";
}
protected static function sexec()
{
return "protected static method return";
}
}
// クロージャで外からprotectedメソッド実行
$closure1 = fn() => $this->exec();
$closure2 = static fn() => Test::sexec();
// クロージャにクラスオブジェクトをバインド(繋げる)
$new_closure1 = Closure::bind($closure1, new Test(), 'Test');
$new_closure2 = Closure::bind($closure2, null, 'Test');
var_dump($new_closure1());
var_dump($new_closure2());
string(23) "protected method return"
string(30) "protected static method return"
やっぱりね。外からはprivateと同じだから。
最初のサンプルだけ bindTo() を使った方法もお見せしましたが、このサンプルでもできます。
(クラスインスタンスならcall()もできる。同じことを何回も書くのもなんなので割愛。)
クラスのメソッドになるクロージャの使い道
クロージャを実行すると、あたかもクラス内のメソッドかのように処理が行われます。
プロパティ値の参照もできるし変更もできる。
クロージャのバインドはたんに連携しているだけじゃありません。合体に近い。クロージャはあとから追加されるクラスメソッドです。
よく、パッケージ等でサードパーティ製のクラスなどを使いますが、こういうメソッドがあったら良かったのにな、と思うことがあります。
そういうときに使える。自分の好きなメソッドを作れると言ってもいい。
ここまでできるといろんな使い道がありそうですが、セキュリティ的にどうなんだろうと思う。
隠蔽といっても100%じゃないことに気をつけよう!
クラスの隠蔽は、100%匿ってくれるとは限りません。JavaやC++でも同じ。
クラスオブジェクトは、エラーログなどどうしても内部の情報を出さざるを得ない状況があります。
そういうクラスを使えばprivateでも参照することは可能。
Closureクラスも同じように、外部からオブジェクト内部の情報が見れる機能があるということです。
PHP公式ドキュメント