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

[BEARのAOP](http://bearsunday.github.io/manuals/1.0/ja/aop.html) をよく使っています。この記事はBEARでインターセプターを実装するときの私の覚え書きです。

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

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

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

- 必要に応じてモジュールの分割を行う（独立した機能モジュールでのインターセプター、カスタムアノテーションの設計）。
- 例： 認可のアノテーション `Accessible` を作成したい場合

```php
<?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]
        );
    }
}
```

```php
use MyVendor\MyPackage\Security\Module\Accessible;

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

## マッチャー（Matcher）

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

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

```php
// リソースのメソッドのためのカスタムマッチャー
class IsHttpMethodMatcher extends EqualsToMatcher
{
    public function __construct()
    {
        parent::__construct([
            'onGet',
            'onPost',
            'onPut',
            'onPatch',
            'onDelete',
            'onHead',
        ]);
    }
}
```

```php
// 上記 `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));
    }
}
```

```php
// モジュールでの束縛
$this->bindInterceptor(
    $this->matcher->subclassesOf(ResourceObject::class),
    new IsHttpMethodMatcher(),
    [FooInterceptor::class]
);
```

上記は、[@iteman](https://twitter.com/iteman) さんによるプライベートプロジェクトのコードを一部改変したものです。下記パッケージにしてあります。

- [FormalBears.Aop](https://github.com/kumamidori/FormalBears.Aop)

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

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

- インターセプターに複雑な仕事をさせる場合はサービスの利用を検討
- サービスにバリエーションの考慮が不要な場合はサービスを直接 `アンターゲット束縛` [^3] してインターセプターへインジェクト

```
インターセプター --- depends ---> サービス
```

- フレームワークとサービスの境界を設計 （扱う情報の正規化はサービスに引き渡す前に済ませる）

## まとめ

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

[^1]: 　`@Named` （名前束縛） ： [Ray.Di](https://github.com/ray-di/Ray.Di) のビルトインアノテーションで **名前** を指定して束縛します
[^2]: 　カスタムマッチャーの作成手順については[BEARマニュアル](http://bearsunday.github.io/manuals/1.0/ja/aop.html)参照
[^3]: 　アンターゲット束縛：コンクリートクラスの束縛ができます（[Ray.Di](https://github.com/ray-di/Ray.Di) マニュアル参照）