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