PHPで__call()および__callStatic()を使用してメソッド呼び出しをインターセプトするときの機能

プロローグ



オブジェクト指向パラダイムでの私の実験は、この記事を書くのに役立ちました。

実際、この記事はphpの達人にとっては面白そうに思えませんが、言語を理解するだけで、落とし穴を回避できるようになることを望んでいます。 この記事は、OOPのマニュアルであるとは主張していませんが、いくつかの点を明確にしているだけです。



__call()および__callStatic()とは何ですか?



簡単なものから始めましょう。オブジェクトのメソッドとプロパティを記述するクラスがあります(原則的に論理的です)。 このオブジェクトの存在しないメソッドを参照することにしたと想像してください。 あなたは何を得ますか? そうです-致命的な間違いです! 以下は最も単純なコードです。



<?php class OurClass{} $Object=new OurClass; $Object->DynamicMethod(); # Fatal error: Call to undefined method OurClass::DynamicMethod() ?>
      
      







静的なコンテキストでは、同様の動作が観察されます。



 <?php class OurClass{} OurClass::StaticMethod(); # Fatal error: Call to undefined method OurClass::StaticMethod() ?>
      
      







そのため、必要なメソッドがない場合にコードを実行するか、呼び出しを試みたメソッドを見つけるか、別のAPIを使用して必要なメソッドを呼び出すことが必要になる場合があります。 この目的のために、__call()および__callStatic()メソッドがあります-それらは、それぞれオブジェクトのコンテキストと静的コンテキストで存在しないメソッドへの呼び出しをインターセプトします。

「マジックメソッド」を使用して例を書き換えます。 注:これらのウィザードはそれぞれ2つのパラメーターを取ります。1つ目は呼び出しようとしているメソッドの名前、2つ目は呼び出されたメソッドのパラメーターを含むリストです。 キーは呼び出されたメソッドのパラメーター番号、値はパラメーター自体です:



 <?php class OurClass { public function __call($name,array $params) { echo '   $Object->'.$name.',    ,    '.__METHOD__.'()<br>' .PHP_EOL; return; } public static function __callStatic($name,array $params) { echo '   '.__CLASS__.'::'.$name.',    ,    '.__METHOD__.'()'; return; } } $Object=new OurClass; #   $Object->DynamicMethod,    ,    OurClass::__call() $Object->DynamicMethod(); #   OurClass::StaticMethod,    ,    OurClass::__callStatic() OurClass::StaticMethod(); ?>
      
      







これら2人の同志の実際の応用は、あなたの想像力にのみ依存します。 例として、Fluent Interfaceプログラミング手法の実装のスケッチを示します(一部はこれをデザインパターンと見なしますが、その本質は名前から変わりません)。 要するに、流れるようなインターフェイスを使用すると、オブジェクトへの一連の呼び出しを行うことができます(見た目はjQueryに似ています)。 ハブには、この種のアルゴリズムの実装に関する記事がいくつかあります。 壊れたロシア語の翻訳では、流fluentなインターフェースは「流体インターフェース」のように聞こえます。



 <?php abstract class Manager { public $content_storage=''; public function __toString() { return $this->content_storage; } public function __call($name,array $params) { $this->content_storage.=$this->_GetObject($name,$params).'<br>'.PHP_EOL; return $this; } } abstract class EntryClass { public static function Launch() { return new FluentInterface; } } class FluentInterface extends Manager { public function __construct() { /** * -  */ } public static function _GetObject($n,array $params) { return $n; } } echo $FI=EntryClass::Launch() ->First() ->Second() ->Third(); /*  First Second Third */ ?>
      
      







傍受機能について何か教えてください。



間違いなく教えます。 昨日コンピューターに座って、PHPの知識を体系化することにしました。

このコードのようなものをスケッチしました(元のコードでは少し異なりましたが、残りはこの問題のセマンティックロードを持たないため、記事ではそれを減らしました)。



 <?php abstract class Base { public function __call($n,array$p) { echo __METHOD__.' says: '.$n; } } class Main extends Base { public function __construct() { self::Launch(); } } $M=new Main; ?>
      
      







ページを更新しました。 私の驚きは際限がありませんでした。 私はすぐにphp.netで実行してマニュアルを見ました。

これはドキュメントからの抜粋です

 public mixed __call ( string $name , array $arguments ) public static mixed __callStatic ( string $name , array $arguments )
      
      





オブジェクトのコンテキストで、アクセスできないメソッドを呼び出すと、__ call()メソッドが呼び出されます。

静的コンテキストでは、アクセスできないメソッドを呼び出すと、__ callStatic()メソッドが呼び出されます。


長い間、何が問題なのか理解できませんでした。 PHPバージョン:5.4.13。 つまり、任意のコンテキストから存在しないメソッドへの呼び出しが__call()につながっていた時代は長くなりました。 論理的な致命的エラーの代わりに、なぜ__call()呼び出しを受け取るのですか? さらに調査を続けました。 __callStatic()メソッドを抽象基本クラスに追加しました。 ページを再度更新しました。 呼び出しはまだ__call()で対処されました。 半日苦しみましたが、私はまだ問題が何であるかを認識しました。 PHPは、クラス内とクラス外の静的コンテキストを異なる方法で認識することがわかりました。 分かりませんか? 説明しようとします。 前の例を使用して、1行追加します。



 <?php abstract class Base { public function __call($n,array$p) { echo __METHOD__.' says: '.$n; } } class Main extends Base { public function __construct() { self::Launch(); } } $M=new Main; Main::Launch(); #    .    Fatal error: Call to undefined method Main::Launch() ?>
      
      







つまり、静的コンテキスト-不一致の静的コンテキスト。

奇跡など。 「魔法の方法」を勉強したとき、文字通りその名前をとるべきだとは思いませんでした。



さて、ここですべてが明らかになります。上記の例のBaseクラスに__callStatic()メソッドを追加すると、致命的なエラーを表示する代わりに、PHPは__callStatic()を実行します。



まとめ



すべてが明確でない場合: クラスインスタンス内の静的メソッドの呼び出しとクラスインスタンス外の静的メソッドの呼び出しは、インタープリターによって異なる方法解釈されるという事実について話します。 self :: Launch()をMain :: Launch()に変更しても、コールコンテキストは変更されません。 この場合の動作は同じです。 繰り返しますが、私は説明します:



 <?php abstract class Base { public function __call($n,array$p) { echo __METHOD__.' says: '.$n; } } class Main extends Base { public function __construct() { #      Base::__call() self::Launch(); static::Launch(); Main::Launch(); } } $M=new Main; ?>
      
      







この記事の結果は単純です。メソッドを呼び出すときは注意して(ただし、いつものように)動作を確認してください。



追記



バグトラッカーを見てみたところ、 問題は1つもありませんでした 。 しかし、開発者はそのようなバグはバグではないと明らかに判断したため、そのままにしておきました。



All Articles