BEARでインターセプターを実装する時の覚え書き(アノテーション、マッチャ)

BEARのAOP をよく使っています。この記事はBEARでインターセプターを実装するときの私の覚え書きです。

カスタムアノテーションを作るべきとき

  • 「単にアプリケーションの設定値を束縛したい」という用途であれば、必ずしもカスタムアノテーションを用意する必要はない。その用途ならばビルトインアノテーションの @Named 1 が簡易的で便利。
  • 基本的にはカスタムアノテーションクラスを作成することを検討。

モジュールとカスタムアノテーション

  • 必要に応じてモジュールの分割を行う(独立した機能モジュールでのインターセプター、カスタムアノテーションの設計)。
  • 例: 認可のアノテーション Accessible を作成したい場合
<?php
namespace MyVendor\MyPackage\Security\Module;

//  (`AppModule` に直接束縛を書くのではなく、) 
// セキュリティを扱う独立した機能として `SecurityModule` を作成、
// その中で認可機能を束縛。
class SecurityModule extends AbstractModule
{
    public function configure()
    {
        $this->bindInterceptor(
            $this->matcher->subclassesOf(ResourceObject::class),
            $this->matcher->annotatedWith(Accessible::class),
            [AccessibleInterceptor::class]
        );
    }
}
use MyVendor\MyPackage\Security\Module\Accessible;

// `SecurityModule` のアノテーションを利用
/**
 * @Accessible("administrator")
 */
public function onGet(string $articleId)
{
    // ...
}

マッチャー(Matcher)

  • モジュールの束縛で適切に対象を指定(インターセプタークラス内で実行対象をチェックしているとしても明示的に設定)
  • 必要に応じてカスタムマッチャー 2 を作成

リソースのHTTPメソッドをインターセプトしたい場合の実装例

// リソースのメソッドのためのカスタムマッチャ
class IsHttpMethodMatcher extends EqualsToMatcher
{
    public function __construct()
    {
        parent::__construct([
            'onGet',
            'onPost',
            'onPut',
            'onPatch',
            'onDelete',
            'onHead',
        ]);
    }
}
// 上記 `IsHttpMethodMatcher` のスーパークラス。
// 同一かどうかのカスタムマッチャ。
use Ray\Aop\AbstractMatcher;

class EqualsToMatcher extends AbstractMatcher
{
    /**
     * @var array
     */
    private $values;

    /**
     * @param string|array $values
     */
    public function __construct($values)
    {
        parent::__construct();

        $this->values = (array) $values;
    }

    /**
     * {@inheritdoc}
     */
    public function matchesClass(\ReflectionClass $class, array $arguments)
    {
        return in_array(strtolower($class->getName()), array_map('strtolower', $this->values));
    }

    /**
     * {@inheritdoc}
     */
    public function matchesMethod(\ReflectionMethod $method, array $arguments)
    {
        return in_array(strtolower($method->getShortName()), array_map('strtolower', $this->values));
    }
}
// モジュールでの束縛
$this->bindInterceptor(
    $this->matcher->subclassesOf(ResourceObject::class),
    new IsHttpMethodMatcher(),
    [FooInterceptor::class]
);

※上記マッチャーのスニペットは @iteman さんのプライベートプロジェクトのコードを一部改変して掲載させて頂きました。ありがとうございます。

※上記のリソースのインターセプターの用途としては自分のドメインに合ったフレームワークを作りたいときを想定しています。BEARは標準セットで使うこともできますが、拡張に対して開かれているので自分のドメインにあったフレームワークを作っていくアプローチを採ることも可能です。

インターセプターとサービスの協調

  • インターセプターに複雑な仕事をさせる場合はサービスの利用を検討
  • サービスにバリエーションの考慮が不要な場合はサービスを直接 アンターゲット束縛 3 してインターセプターへインジェクト
インターセプター --- depends ---> サービス
  • フレームワークとサービスの境界を設計 (扱う情報の正規化はサービスに引き渡す前に済ませる)

まとめ

BEARでインターセプターを実装する時のトピックを挙げて、リソースをインターセプトしたい場合の実装例も記しました。適切なアノテーション、適切なマッチャを使うことは良いプラクティスだと思います。


  1.  @Named (名前束縛) : Ray.Di のビルトインアノテーションで 名前 を指定して束縛します 

  2.  カスタムマッチャの作成手順についてはBEARマニュアル参照 

  3.  アンターゲット束縛:コンクリートクラスの束縛ができます(Ray.Di マニュアル参照) 

Sign up for free and join this conversation.
Sign Up
If you already have a Qiita account log in.