リフレクションなしでPHPのオブジェクトのプライベートプロパティにアクセスする

数週間前、 ProxyManagerの 問題取り組みました 。 問題は単純でした: ReflectionClassReflectionPropertyは非常に、非常に、そして非常に遅いです!

この研究の理由は、初期化のオーバーヘッドなしで大量のデータを扱うためにハイドレーターを最適化しようとする私の試みです。



PHP 5.4が役立ちます!





PHP 5.4では、クロージャーとClosure#bind()



メソッド用の新しいAPIが提供されました。

Closure#bind()



使用すると、基本的に、特定のクラスまたはオブジェクトのスコープを持つクロージャーのインスタンスを取得できます。 優雅に! これは、既存のオブジェクトにAPIを追加する方法です!

必要に応じてオブジェクトのカプセル化を解除しましょう。

プライベートプロパティのアクセス方法は既にドキュメントで説明されていますが、とにかく例を示します。

私たちは私有財産のKitchen#yummy



を盗みますKitchen#yummy





 <?php class Kitchen { private $yummy = 'cake'; }
      
      





このフィールドを取得するクロージャーを定義します。

 <?php $sweetsThief = function (Kitchen $kitchen) { return $kitchen->yummy; }
      
      





Kitchen



インスタンスからyummy



を盗みます:

 <?php $kitchen = new Kitchen(); var_dump($sweetsThief($kitchen));
      
      





残念ながら、 $sweetsThief



致命的なエラーが発生します。

 Fatal error: Cannot access private property Kitchen::$yummy in [...] on line [...]
      
      





Closure#bind()



よりも泥棒を賢くしましょう:

 <?php $kitchen = new Kitchen(); // Closure::bind()       $sweetsThief = Closure::bind($sweetsThief, null, $kitchen); var_dump($sweetsThief($kitchen));
      
      





頑張って



クロージャ::バインドvsリフレクション:パフォーマンス



100,000回の初期化反復の簡単なベンチマークを作成しました。

 <?php for ($i = 0; $i < 100000; $i += 1) { $sweetsThief = Closure::bind(function (Kitchen $kitchen) { return $kitchen->yummy; }, null, 'Kitchen'); } <?php for ($i = 0; $i < 100000; $i += 1) { $sweetsThief = new ReflectionProperty('Kitchen', 'yummy'); $sweetsThief->setAccessible(true); }
      
      





新しくコンパイルされたPHP 5.5(Ubuntu 13.04 amd64ボックス)では、最初のテストに0.325秒かかり、2番目のテストに0.658かかりました



ここの反射はずっと遅いです。 (49%)



しかし、これはまったく興味深いことではありません。少なくとも私にとっては、同じものを100,000回初期化する必要はないからです。 本当に興味深いのは、プライベートプロパティへのアクセスです。 私もこれをテストしました:

 <?php $kitchen = new Kitchen(); $sweetsThief = Closure::bind(function (Kitchen $kitchen) { return $kitchen->yummy; }, null, 'Kitchen'); for ($i = 0; $i < 100000; $i += 1) { $sweetsThief($kitchen); } <?php $kitchen = new Kitchen(); $sweetsThief = new ReflectionProperty('Kitchen', 'yummy'); $sweetsThief->setAccessible(true); for ($i = 0; $i < 100000; $i += 1) { $sweetsThief->getValue($kitchen); }
      
      





最初のテストには約0.110秒かかり、2番目のテストには0.199秒かかりました。

これはリフレクションよりもはるかに高速で、印象的です! (55%)



リンクによるプライベートプロパティへのアクセス



もう1つの利点があります。リフレクションの代わりにクロージャーを使用すると、リンクを使用してオブジェクトプロパティを操作できます。

 <?php $sweetsThief = Closure::bind(function & (Kitchen $kitchen) { return $kitchen->yummy; }, null, $kitchen); $cake = & $sweetsThief($kitchen); $cake = 'lie'; var_dump('the cake is a ' . $sweetsThief($kitchen));
      
      







ユニバーサルアクセス方法



上記を考えると、任意のオブジェクトのプロパティを取得する単純なラッパーを作成できます。

 <?php $reader = function & ($object, $property) { $value = & Closure::bind(function & () use ($property) { return $this->$property; }, $object, $object)->__invoke(); return $value; }; $kitchen = new Kitchen(); $cake = & $reader($kitchen, 'cake'); $cake = 'sorry, I ate it!'; var_dump($kitchen);
      
      





作業例

私たちは、どこからでも、さらには参照によって、あらゆる資産にアクセスできます。 やった! 再び規則を破りました!



おわりに



繰り返しになりますが、PHPは同時に良い面と悪い面があります。 これはひどい構文を備えたひどい言語ですが、リリースごとに新しい機能を提供してくれる言語の制限を回避することができます。

私はこの手法を自分では使用しませんが、オブジェクトのプライベートプロパティを取得する必要がある場合は、参照によってもそのようにします。



免責事項:この機能は注意して使用してください!



All Articles