Ray.Aop パッケージはメソッドインターセプションの機能を提供します。マッチするメソッドが実行される度に実行されるコードを記述する事ができます。トランザクション、セキュリティやログといった横断的な”アスペクト”に向いています。なぜならインターセプターが問題をオブジェクトというよりアスペクトに分けるからです。これらの用法はアスペクト指向プログラミング(AOP)と呼ばれます。
Matcher は値を受け取ったり拒否したりするシンプルなインターフェイスです。例えばRay.Aopでは2つの Matcher が必要です。1つはどのクラスに適用するかを決め、もう1つはそのクラスのどのメソッドに適用するかを決めます。これらを簡単に利用するためのファクトリークラスがあります。
MethodInterceptors はマッチしたメソッドが呼ばれる度に実行されます。呼び出しやメソッド、それらの引き数、インスタンスを調べる事ができます。横断的なロジックと委譲されたメソッドが実行されます。最後に返り値を調べて返します。インターセプターは沢山のメソッドに適用され沢山のコールを受け取るので、実装は効果的で透過的なものになります。
メソッドインターセプターがRay.Aopでどのように機能するかを明らかにするために、週末にはピザの注文を禁止するようにしてみましょう。デリバリーは平日だけ受け付ける事にして、ピザの注文を週末には受け付けないようにします!この例はAOPで認証を使用するときにのパターンと構造的に似ています。
週末だけにするためのアノテーションを定義します。
<?php
/**
* NotOnWeekends
*
* @Annotation
* @Target("METHOD")
*/
final class NotOnWeekends
{
}そして、インターセプトするメソッドに適用します。
<?php
class RealBillingService
{
/**
* @NotOnWeekends
*/
chargeOrder(PizzaOrder $order, CreditCard $creditCard)
{次に、org.aopalliance.intercept.MethodInterceptorインターフェイスを実装したインターセプターを定義します。元のメソッドを実行するためには $invocation->proceed() と実行します。
<?php
class WeekendBlocker implements MethodInterceptor
{
public function invoke(MethodInvocation $invocation)
{
$today = getdate();
if ($today['weekday'][0] === 'S') {
throw new \RuntimeException(
$invocation->getMethod()->getName() . " not allowed on weekends!"
);
}
return $invocation->proceed();
}
}設定完了しました。このコードでは「どのクラスでも」「メソッドに@NotOnWeekendsアノテーションがある」という条件にマッチします。
<?php
use Ray\Aop\Sample\Annotation\NotOnWeekends;
use Ray\Aop\Sample\Annotation\RealBillingService;
$pointcut = new Pointcut(
(new Matcher)->any(),
(new Matcher)->annotatedWith(NotOnWeekends::class),
[new WeekendBlocker]
);
$bind = (new Bind)->bind(RealBillingService::class, [$pointcut]);
$billing = (new Compiler($tmpDir))->newInstance(RealBillingService::class, [], $bind);
try {
echo $billing->chargeOrder();
} catch (\RuntimeException $e) {
echo $e->getMessage() . "\n";
exit(1);
}全てをまとめ(土曜日まで待って)、メソッドをコールするとインターセプターにより拒否されます。
chargeOrder not allowed on weekends!
<?php
$bind = (new Bind)->bindInterceptors('chargeOrder', [new WeekendBlocker]);
$compiler = new Compiler($tmpDir);
$billing = $compiler->newInstance('RealBillingService', [], $bind);
try {
echo $billing->chargeOrder();
} catch (\RuntimeException $e) {
echo $e->getMessage() . "\n";
exit(1);
}独自のマッチャーを作成することもでます。
contains マッチャーを作成するためには、2つのメソッドを持つクラスを提供する必要があります。
1つはクラスのマッチを行うmatchesClassメソッド、もう1つはメソッドのマッチを行うmatchesMethodです。いずれもマッチしたかどうかをブールで返します。
use Ray\Aop\AbstractMatcher;
use Ray\Aop\Matcher;
class IsContainsMatcher extends AbstractMatcher
{
/**
* {@inheritdoc}
*/
public function matchesClass(\ReflectionClass $class, array $arguments) : bool
{
list($contains) = $arguments;
return (strpos($class->name, $contains) !== false);
}
/**
* {@inheritdoc}
*/
public function matchesMethod(\ReflectionMethod $method, array $arguments) : bool
{
list($contains) = $arguments;
return (strpos($method->name, $contains) !== false);
}
}$pointcut = new Pointcut(
(new Matcher)->any(),
new IsContainsMatcher('charge'),
[new WeekendBlocker]
);
$bind = (new Bind)->bind(RealBillingService::class, [$pointcut]);
$billing = (new Compiler($tmpDir))->newInstance(RealBillingService::class, [], $bind);インターセプターの実行順は以下のルールで決定されます。
- 基本的にはバインドした順に実行されます。
PriorityPointcutで定義したものが最も優先されます。- アノテーションでメソッドマッチするものは
PriorityPointcutの次に優先されます。その時アノテートされた順で優先されます。
/**
* @Auth // 1st
* @Cache // 2nd
* @Log // 3rd
*/この機能の背後ではメソッドのインターセプションを事前にコードを生成する事で可能にしています。Ray.Aopはダイナミックにサブクラスを生成してメソッドをオーバーライドするインターセプターを適用します。
クラスとメソッドは以下のものである必要があります。
- クラスは final ではない
- メソッドは public
呼び出されたメソッドをそのまま実行するだけのインターセプターは以下のようになります。
class MyInterceptor implements MethodInterceptor
{
public function invoke(MethodInvocation $invocation)
{
// メソッド実行前
//
// メソッド実行
$result = invocation->proceed();
// メソッド実行後
//
return $result;
}
}インターセプターに渡されるメソッド実行(MethodInvocation)オブジェクトは以下のメソッドを持ちます。
$invocation->proceed()- メソッド実行$invocation->getMethod()- メソッドリフレクションの取得$invocation->getThis()- オブジェクトの取得$invocation->getArguments()- 引数の取得$invocation->getNamedArguments()- 名前付き引数の取得
拡張されたリフレクションはアノテーション取得のメソッドを持ちます。
/** @var $method \Ray\Aop\ReflectionMethod */
$method = $invocation->getMethod();
/** @var $class \Ray\Aop\ReflectionClass */
$class = $invocation->getMethod()->getDeclaringClass();$method->getAnnotations()- メソッドアノテーションの取得$method->getAnnotation($name)$class->->getAnnotations()- クラスアノテーションの取得$class->->getAnnotation($name)
このメソッドインターセプターのAPIはAOPアライアンスの部分実装です。
- PHP 5.6+
- hhvm
Ray.Aopの推奨インストール方法は、Composerでのインストールです。
# Ray.Aop を依存パッケージとして追加する
$ composer require ray/aop ~2.0Ray.Aopをソースからインストールし、ユニットテストとデモを実行するには次のようにします。
git clone https://github.com/ray-di/Ray.Aop.git
cd Ray.Aop
composer install
vendor/bin/phpunit
php demo/run.phpDIとAOPを統合したDIフレームワークRay.Diもご覧ください。
- この文書の大部分は Guice/AOP から借用しています。

