AOP

アスペクト指向プログラミングは、横断的関心事の問題を解決します。対象メソッドの前後に任意の処理をインターセプターで織り込むことができます。 対象となるメソッドはビジネスロジックなど本質的関心事のみに関心を払い、インターセプターはログや検証などの横断的関心事に関心を払います。

BEAR.SundayはAOP Allianceに準拠したアスペクト指向プログラミングをサポートします。

インターセプター

インターセプターのinvokeメソッドで$invocationメソッド実行変数を受け取り、メソッドの前後に処理を加えます。 この変数は、インターセプター元メソッドを実行するためだけの変数です。前後にログやトランザクションなどの横断的処理を記述します。

use Ray\Aop\MethodInvocation;

class MyInterceptor implements MethodInterceptor
{
    public function invoke(MethodInvocation $invocation)
    {
        // メソッド実行前の処理
        // ...

        // メソッド実行
        $result = $invocation->proceed();

        // メソッド実行後の処理
        // ...

        return $result;
    }
}

束縛

モジュールで対象となるクラスとメソッドをMatcherで”検索”して、マッチするメソッドにインターセプターを束縛します。

$this->bindInterceptor(
    $this->matcher->any(),                   // どのクラスでも
    $this->matcher->startsWith('delete'),    // "delete"で始まるメソッド名のメソッドには
    [Logger::class]                          // Loggerインターセプターを束縛
);

$this->bindInterceptor(
    $this->matcher->SubclassesOf(AdminPage::class),  // AdminPageの継承または実装クラスの
    $this->matcher->annotatedWith(Auth::class),      // @Authアノテーションがアノテートされているメソッドには
    [AdminAuthentication::class]                     // AdminAuthenticationインターセプターを束縛
);

Matcherは他にこのような指定もできます。

インターセプターに渡されるMethodInvocationで対象のメソッド実行に関連するオブジェクトやメソッド、引数にアクセスすることができます。

リフレクションのメソッドでアノテーションを取得することができます。

$method = $invocation->getMethod();
$class = $invocation->getMethod()->getDeclaringClass();
  • $method->getAnnotations() - メソッドアノテーションの取得
  • $method->getAnnotation($name)
  • $class->getAnnotations() - クラスアノテーションの取得
  • $class->getAnnotation($name)

カスタムマッチャー

独自のカスタムマッチャーを作成するためにはAbstractMatchermatchesClassmatchesMethodを実装したクラスを作成します。

contains マッチャーを作成するためには、2つのメソッドを持つクラスを提供する必要があります。 1つはクラスのマッチを行うmatchesClassメソッド、もう1つはメソッドのマッチを行うmatchesMethodメソッドです。いずれもマッチしたかどうかをboolで返します。

use Ray\Aop\AbstractMatcher;
use Ray\Aop\Matcher;

/**
 * 特定の文字列が含まれているか
 */
class ContainsMatcher 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);
    }
}

モジュール

class AppModule extends AbstractModule
{
    protected function configure()
    {
        $this->bindInterceptor(
            $this->matcher->any(),
            new ContainsMatcher('user'), // 'user'がメソッド名に含まれてるか
            [UserLogger::class]
        );
    }
};