PHPには、このプログラミングパラダイムの実装がいくつかあります。 残念ながら、それらの中で、既存の大規模プロジェクトに簡単に実装でき、コードの美的要件を満たすソリューションを見つけることができませんでした。
PHPでのAOP実装テクノロジー
魔法の方法
最も簡単な解決策は、__ callおよび__callStaticの「 マジックメソッド 」を使用することです。 これらのメソッドは、存在しないクラスメソッドにアクセスするときに(クラスで定義されている場合)呼び出されます。 引数として、存在しないメソッドの名前とそれに渡されるパラメーターを取得します。
この場合、アプリケーションは、実際のメソッドがそれらを呼び出す構造で指定された名前とは異なる名前を持つように構築されます。 クロスファンクショナルは「マジックメソッド」に実装されており、必要に応じて制御を実際のクラスメソッドに転送します。
長所:
- 使い始めるのは簡単です。
- 実装には追加のモジュールは必要ありません(ネイティブPHP)。
短所:
- 多数のエンドツーエンド機能を使用することは便利ではありません。
- なぜなら 定義と呼び出しでメソッドの名前が異なるため、IDEでコード補完を使用すると問題が発生します。
コード解析
この方法には、「構文糖」の使用を許可する中間体の存在が含まれます。 必要な機能は、補助構文(xml / json構成、追加のphpクラス、またはコード内の注釈)によって記述され、仲介者によって解析されます。 解析に基づいて、必要な場所にスルー機能の挿入を含む結果のコードが生成されます。
長所:
- 速く動作します 出力は通常のPHPコードであり、自動的に生成されます。
短所:
- 大規模なプロジェクトに実装することは困難です。
- 結果のコードを調整するには、各変更後にコードを解析する必要があります。
実行時のアプリケーションコードの置き換え
悪名高いrunkit拡張機能により、実行中にスクリプトコードを変更できます 。 それに基づいて、タスクを非常に簡単に解決できる小さなライブラリを開発しました。
会う: Annotator
アノテーターの機能
このライブラリは、4種類のハンドラーを実装しています。
情報
Info型ハンドラーは、クラスの処理中にメソッド情報を受け取ります。 これにより、アプリケーションで将来使用するためにメソッドを「登録」できます。 たとえば、それを使用して、特定のURLを処理するメソッドを割り当て、アプリケーションにルーティングを実装できます。
前に
Beforeメソッドのハンドラーは、メソッドが呼び出される前に実行されます。 呼び出されたメソッドに制御を移す前に変更できる入力パラメーターを含む、呼び出されたメソッドに関するすべての必要な情報を受け取ります。
後
呼び出されたメソッドの後に、After型のハンドラーが実行されます。 メソッドとそのパラメーターに関する情報に加えて、呼び出されたメソッドの実行結果も受け取ります。この結果は、必要に応じて置き換えることができます。
周り
呼び出されたメソッドの代わりに、タイプAroundのハンドラーが実行されます。 ハンドラー内では、必要に応じて、呼び出されたメソッドに制御を手動で転送できます。
設置
Annotatorを使用するには、PHP 5.4とrunkitモジュールが必要です。
- ここから拡張機能をダウンロードします: https : //github.com/zenovich/runkit ;
- 収集してインストールします。
phpize && ./configure && make && sudo make install
- すべてがうまくいけば、conf.dまたはphp.iniのrunkit.soモジュールを接続します。
- Annotator.phpクラスをダウンロードして、プロジェクトに接続します。
使用例
このクラスは、すべてのタイプのハンドラーに対して4つの事前予約された注釈を提供しますが、4つのタイプの注釈を登録することもできます。
情報
<?php require_once __DIR__ . '/Annotator.php'; class Advice { public static function infoStatic($point, $options) { var_dump($point); var_dump($options); } } class Test { /** * @info Advice::infoStatic hello world */ public static function testInfoStatic() { return 'info'; } } Annotator::compile('Test');
呼び出し中に既にAnnotator :: compile( 'Test'); AdviceクラスのinfoStaticハンドラーが呼び出され、TestクラスのtestInfoStaticメソッドとハンドラーパラメーターに関する情報を配列配列(「hello」、「world」)として受け取ります。
前に
この例では、標準のアノテーションを使用する代わりにアノテーションを登録し、ハンドラーとして静的クラスメソッドの代わりにオブジェクトメソッドを使用します。
<?php require_once __DIR__ . '/Annotator.php'; class Advice { public function before($point, $params, $options) { $params['string'] = 'bar'; } } class Test { /** * @registered_before */ public function testBefore($string) { return $string; } } $advice = new Advice(); Annotator::register('registered_before', array($advice, 'before'), Annotator::BEFORE); Annotator::compile('Test'); $test = new Test(); echo $test->testBefore('foo');
Annotator :: registerメソッドを使用して、$ adviceオブジェクトのbeforeメソッドに関連付けられた@registered_beforeアノテーションを作成しました。 testBeforeが呼び出されると、制御がハンドラに渡され、$文字列パラメーターが置き換えられ、予期される「foo」の代わりに、「bar」がスクリプトの結果として表示されます。
後
<?php require_once __DIR__ . '/Annotator.php'; class Advice { public function power($point, $params, $options, $result) { return pow($result, $options[0]); } } class Test { /** * @power 4 */ public function testAfter($number) { return $number + 1; } } $advice = new Advice(); Annotator::register('power', array($advice, 'power'), Annotator::AFTER); Annotator::compile('Test'); $test = new Test(); echo $test->testAfter(1);
この例では、testAfterメソッドの結果は4の累乗になります。スクリプトは値16を表示します。
周り
<?php require_once __DIR__ . '/Annotator.php'; class Advice { private static $cache = array(); public function cache($point, $params, $options, $proceed) { if (array_key_exists($options[0], self::$cache)) { // - return self::$cache[$options[0]]; } else { // - $result = $proceed(); // self::$cache[$options[0]] = $result; return $result; } } } class Test { /** * @cache around_cache_key */ public function testAround($string) { return $string; } } $advice = new Advice(); Annotator::register('cache', array($advice, 'cache'), Annotator::AROUND); Annotator::compile('Test'); $test = new Test(); echo $test->testAround('foo') . PHP_EOL; echo $test->testAround('foo') . PHP_EOL; echo $test->testAround('foo') . PHP_EOL;
この例は、単純なキャッシングメカニズムの実装を表しています。 testAroundメソッドは連続して3回呼び出されますが、1回だけ実行されます。 残りの2倍の値は、Adviceクラスの静的変数$キャッシュから取得され、最初の呼び出し後に保存されます。
複数のハンドラーを使用する
アノテーターを使用すると、さまざまなタイプを含む複数のハンドラーを単一のメソッドにアタッチできます。
<?php require_once __DIR__ . '/Annotator.php'; class Advice { public static function before1($point, $params, $options) { $params['string'] .= 'before1'; } public static function before2($point, $params, $options) { $params['string'] .= ' before1'; } public static function after1($point, $params, $options, $result) { return $result . ' after1'; } public static function after2($point, $params, $options, $result) { return $result .= ' after2'; } public static function around1($point, $params, $options, $proceed) { return $proceed() . ' around1'; } public static function around2($point, $params, $options, $proceed) { return $proceed() . ' around2'; } } class Test { /** * @before Advice::before1 * @after Advice::after1 * @around Advice::around1 * @before Advice::before2 * @after Advice::after2 * @around Advice::around2 */ public function testMulti($string) { return $string; } } Annotator::compile('Test'); $test = new Test(); echo $test->testMulti('');
この例の結果、「before1 before1 around1 around2 around1 after1 after2」という行が表示されます。
ハンドラー
ハンドラーの各タイプには、独自のパラメーターセットがあります。
情報
- $ point-ハンドラーがマウントされるメソッド。
- $ options-注釈で指定されたパラメーターの配列。
前に
- $ point-ハンドラーがマウントされるメソッド。
- $ params-呼び出されたときにメソッドに渡されるパラメーターの配列。
- $ options-注釈で指定されたパラメーターの配列。
後
- $ point-ハンドラーがマウントされるメソッド。
- $ params-呼び出されたときにメソッドに渡されるパラメーターの配列。
- $ options-注釈で指定されたパラメーターの配列。
- $ result-ハンドラーがマウントされているメソッドの結果を含む変数。
周り
- $ point-ハンドラーがマウントされるメソッド。
- $ params-呼び出されたときにメソッドに渡されるパラメーターの配列。
- $ options-注釈で指定されたパラメーターの配列。
- $ continueは、ハンドラーがハングしているメソッドに制御を戻す関数です。
あとがき
Annotatorに基づいて、コードを大幅に削減し、アプリケーションの構造を改善し、サポートを簡素化できる便利なメカニズムを非常に簡単かつ迅速に実装できます。 たとえば、AOPをその助けを借りて実装することに加えて、 依存性注入パターンを実装するのは非常に簡単です。
スクリプトの実行中にメソッドを置き換えるには時間がかかることを覚えておく必要があります。 この要求で使用されないクラスについては、Annotator :: compileを呼び出さないでください。 これを行う最も簡単な方法は、phpクラスを自動的にロードすることです。
寿命の長いアプリケーション(単純なデーモンまたはphpDaemonベースのアプリケーション )の場合、導入されたオーバーヘッドはパフォーマンスに実質的に影響を与えません。 クラスは1回だけロードおよび処理されます。