Do You PHP?    
Search Engine Optimization  php5 powerd  Valid XHTML 1.0!  Valid CSS!  このサイトのはてなブックマーク数 



last updated
2006/05/07

counter hits
since 1999/11/06


PECL::interceptを試してみる

Javaの世界(?)ではDIコンテナに絡んでAOP(Aspect Oriented Programming)が注目を集めています。以前、PECLのサイトでいろいろと物色をしていると、「intercept」という文字が。。。「ん?AOP絡みか?」と思い見てみると、説明として「Intercept function/method calls」とありました。「ひょっとして、今までよりも簡単にAOPができるかも?」ということで今回試してみました。

2006/05/06時点の最新版は0.3.0αです。なお、このリリースが2005/05/28と約1年前というのがちょっと気になりますが。。。

使用した環境

まずは試した環境ですが、PHPは以下のようなconfigureオプションを付けたPHP5.1.4です。interceptのページを見る限りPHP4でも使えそうなんですが、手持ちの4.3.11~4.4.2ではコンパイルすらできませんでした。

●PHP5.1.4のconfigureオプション
--with-apxs2=/usr/local/apache2/bin/apxs \
--prefix=/usr/local/lib/php51 \
--with-pear=/usr/local/lib/php51/pear \
--with-config-file-path=/usr/local/lib/php51/ini/5.1.3 \
--with-config-file-scan-dir=/usr/local/lib/php51/ini.d \
--enable-zend-multibyte \
--enable-mbstring \
--enable-mbregex \
--with-dom \
--with-gd=shared \
--with-jpeg-dir \
--with-png-dir \
--with-zlib-dir \
--with-ttf \
--with-freetype-dir \
--enable-gd-jis-conv \
--with-java=shared,/usr/local/jdk \
--with-xsl \
--with-oci8=shared,/u01/app/oracle/product/10.1.0 \
--with-pdo-oci=shared,/u01/app/oracle/product/10.1.0 \
--with-mysql=shared \
--with-pgsql=shared \
--enable-soap=shared \
--enable-debug

「--prefix」オプションを付けているため、phpコマンドのパスがデフォルトとは変わっていますが適宜読み替えてください。

インストール

インストール自体は、毎度おなじみpeclコマンドで行います。簡単ですね。

●intercept拡張モジュールのインストール
# /usr/local/lib/php51/bin/pear config-set preferred_state alpha
# /usr/local/lib/php51/bin/pecl install intercept
# 

インストールが終わったら、拡張モジュールをロードするようphp.iniに追記します。

●php.iniの編集
            :
extension=intercept.so
            :

最後にApacheを再起動し、phpinfoの画面で正しくロードされている事を確認します。

phpinfo画面

動作サンプル

まずはパッケージのREADMEファイルに記載されたサンプルに追記したものです。peclコマンドでインストールした場合、/usr/local/lib/php51/pear/doc/interceptにあります。基本的な使い方もREADMEファイルに記載されていますので、読んでみてください。

●intercept01.php
  
<?php

function myfunc() {
    echo "this is my function<br>";
}

function pre_myfunc() {
    echo "before myfunc()<br>";
}

function post_myfunc() {
    echo "after myfunc()<br>";
}

myfunc();
echo '<hr>';

intercept_add('myfunc', 'pre_myfunc', PRE_INTERCEPT);
intercept_add('myfunc', 'post_myfunc', POST_INTERCEPT);
myfunc();
?>
<hr>
<?php
    show_source($_SERVER['SCRIPT_FILENAME']);
?>

実行結果を見てみると、intercept_add関数を使ってpre_myfunc/post_myfunc関数を追加すると、myfunc関数を呼び出した際にpre_myfunc/post_myfunc関数が自動的に呼び出されている事が分かります。

なお、現バージョン(0.3.0α)では一度intercept_addした関数を取り外すことは、PHPスクリプト終了時までできません。

複数の関数を連続して呼び出すには?

バージョン0.3.0αでのintercept_add関数は、1関数当たり1関数のみ追加できます。つまり、ある関数に対して、複数の関数をintercept_add関数を行うことはできません。これを行うにはどうすればよいか?ということですが、

  • 追加する関数側で連鎖を作る
  • intercept_add関数で追加する関数の連鎖を作る

のどちらかになるのかな?と思います。

次のサンプルでは、前者はfunctionC関数とfunctionD関数の関係、後者はintercept_add関数でfunctionB関数に対してfunctionC関数を追加している部分に相当します。動作サンプルはEXPERIENCEのページでどうぞ。

ただし、後者の場合、無限ループにならないよう気を付ける必要があります。

●intercept02.php
  
<?php
function functionA() {
    echo "this is functionA<br>";
}
function functionB() {
    echo "this is functionB<br>";
}
function functionC() {
    echo "this is functionC<br>";
    functionD();
}
function functionD() {
    echo "this is functionD<br>";
}

intercept_add('functionA', 'functionB', PRE_INTERCEPT);
intercept_add('functionB', 'functionC', POST_INTERCEPT);
intercept_add('functionA', 'functionC', POST_INTERCEPT);
functionA();
?>
<hr>
<?php
    show_source($_SERVER['SCRIPT_FILENAME']);
?>

メソッドへの適用は?

今までは関数に対して関数を追加していましたが、メソッドについてはどうなのか?こちらも調べてみました。結果からいうと、

  • クラスメソッドは可能
  • インスタンスメソッドは不可能

のようです(残念)。また、第1引数の方のみ使用することができます。インスタンスメソッドの場合、クラス名とメソッド名を値とする配列としてintercept_add関数に渡せばよいハズですが、TODOファイルを見る限り、このバージョンではここまでの実装となっているようです。また「クラス名::メソッド名」とした場合でもPHP内部で関数と同じ扱いになるのか、あっさりと動きました。動作サンプルはEXPERIENCEのページでどうぞ。

●intercept03.php
  
<?php
class ClassA
{
    static function methodA()
    {
        echo 'ClassA::methodA called<br>';
    }

    static function methodB()
    {
        echo 'ClassA::methodB called<br>';
    }
}
function functionA()
{
    $ret = 1;
    echo 'functionA called<br>';
    return $ret;
}
function intercept_function()
{
    $ret = 3;
    echo 'intercept_function called<br>';
    return $ret;
}
?>
<?php
intercept_add('functionA', 'intercept_function', POST_INTERCEPT);
echo 'return value=' . functionA() . '<hr>';

intercept_add('ClassA::methodA', 'intercept_function', PRE_INTERCEPT);
intercept_add(array('ClassA', 'methodA'), 'intercept_function', POST_INTERCEPT);

ClassA::methodA();
$a = new ClassA();
?>
<hr>
<?php
    show_source($_SERVER['SCRIPT_FILENAME']);
?>

AOPでありがちなLog出力の例

AOPのサンプルではありがちですが、ある関数を呼び出した際、自動的にロギングするというものです。ここではPEAR::Logを使用しています。ついでにPEAR::Benchmarkを使って、関数のベンチマークを取るようにもしてみました。また、このベンチマーク結果をログにも出力しています。動作サンプルはEXPERIENCEのページでどうぞ。ちなみに、ログファイルには以下のように出力されます。実際のログファイルははEXPERIENCEのページにありますが、毎分消してますので注意してください。

●intercept04.log
May 06 22:55:12 acpect_logging [debug] entering function
May 06 22:55:12 acpect_logging [debug] time:0.0010161399841309 sec
May 06 22:55:12 acpect_logging [debug] time:0.0013229846954346 sec
●intercept04.php
  
<?php
require_once 'Log.php';
require_once 'Benchmark/Profiler.php';
?>
<?php
function myfunc1() {
    echo "this is my function 1<br>";
}

function myfunc2() {
    echo "this is my function 2<br>";
}

function acpect_logging() {
    global $_GlobalVariables;
    $_GlobalVariables->log->debug('entering function');
}

function acpect_bench_pre() {
    global $_GlobalVariables;
    $_GlobalVariables->profiler->start();
}

function acpect_bench_post() {
    global $_GlobalVariables;
    $_GlobalVariables->profiler->stop();
    $info = $_GlobalVariables->profiler->getSectionInformations();
    echo 'time:' . $info['time'] . ' sec<br>';
    if (isset($_GlobalVariables->log)) {
        $_GlobalVariables->log->debug('time:' . $info['time'] . ' sec');
    }
}

/**
 * 素のまま呼び出してみる
 */
myfunc1();
myfunc2();
echo '<hr>';

/**
 * 共通なオブジェクトを格納するオブジェクト
 */
$_GlobalVariables = new stdClass();

/**
 * ロガーとプロファイラの準備
 */
$_GlobalVariables->log =& Log::factory('file', 'intercept04.log', 'acpect_logging');
$_GlobalVariables->profiler = new Benchmark_Profiler();

/**
 * myfuncにアスペクトを追加
 */
intercept_add('myfunc1', 'acpect_logging', PRE_INTERCEPT);
intercept_add('acpect_logging', 'acpect_bench_pre', PRE_INTERCEPT);
intercept_add('myfunc1', 'acpect_bench_post', POST_INTERCEPT);
intercept_add('myfunc2', 'acpect_bench_pre', PRE_INTERCEPT);
intercept_add('myfunc2', 'acpect_bench_post', POST_INTERCEPT);

/**
 * 再度呼び出してみる
 */
myfunc1();
myfunc2();
?>
<hr>
<?php
    show_source($_SERVER['SCRIPT_FILENAME']);
?>

まとめ

intercept拡張モジュールを一通り試してみました。まだα版ということで、機能的には必要最低限しか用意されていませんので、実際の適用範囲がかなり狭いものと思います。添付のTODOファイルにはロードマップが記載されていますが、もうちょっとバージョンが上がるのを期待したいところですが、現バージョンが1年前にリリースされていることもあり、正直「どうかな~?」といった感じです。

ただ、機能的には面白いと思いますし、パフォーマンス面もPHPスクリプトでAOPをやる場合と比べて有利なハズですので、正式にリリースされれば既存のAOPフレームワークが乗り換える可能性もあります。

いずれにしても、もうちょっと注目しておきたい拡張モジュールであることに変わりはありませんね。



About This Site |  Privacy Policy |  Contact
Copyright © 1999 - 2006 by Hideyuki SHIMOOKA all rights reserved.