BEAR.Sunday 学習記録(5) 別アプリケーションのリソースを呼ぶ

> なんちゃってエンジニアが BEAR.Sunday であれこれ作ってみたい、という勉強の記録です。怪しい記述があったり、微妙に間違っていたり、盛大に間違っていたりすると思いますので参考にされる場合はご注意ください。
また、試行錯誤しながら書き進めているため、理路整然とした記事にはなっておりません。すみません。
PHP のオブジェクト指向はかろうじて理解していますが、PHP5.4 以降の機能や BEAR.Sunday でも使われている DI や AOP はぼんやりと概念は理解した、というレベルです。

以下では、BEAR.Sunday で別アプリケーションのリソースを呼ぶ方法を探っています。
残念ながらいくつかの問題は未解決ですが、ひとまず公開いたします。

## 概要
ある BEAR.Sunday アプリケーションから別の BEAR.Sunday アプリケーションのリソースを利用したい場合、（たぶん）ホストを分けてやる必要があります。

通常、BEAR.Sunday ではリソースを利用する場合、次のようなコードを書きます。

```php
$result = $this->resource->get->uri('app://self/foo')->request();
```

この `uri()` の引数になっている文字列 `'app://self'` は、このアプリケーション内部のAPIを利用する場合の書き方で、別アプリケーションの API を利用する場合、たとえば Myapp.Sub アプリケーションのAPIを利用したい場合は、次のように書きます。

```php
$result = $this->resource->get->uri('app://sub/bar')->request();
```

`app://self` が `app://sub` に変わっているわけですが、この "sub" というホスト名（ホスト名は任意ですが、ここでは sub という名前にしました）と Myapp.Subというアプリケーションの関連づけは、自動的にやってくれるわけではありません。いくつかの作業が必要となります。

## アプリケーションの準備

```shell-session
// BEAR.Sunday のインストール
php composer.phar create-project bear/package bear

// アプリケーション（Myapp.Main と Myapp.Sub） のひな形を生成
php composer.phar create-project bear/skeleton bear/apps/Myapp.Main
php composer.phar create-project bear/skeleton bear/apps/Myapp.Sub
```

Myapp.Main から Myapp.Sub のリソースを呼ぶ処理は適当に作ってください。

## 手順

### (1) autoload する
利用したいアプリケーションをオートロードの対象に追加します。

```php:bear/apps/Myapp.Main/bootstrap/autoload.php
// （略）

Bootstrap::registerLoader(
    $loader,
    'Myapp\Sub',
    $packageDir.'/apps/Myapp.Sub'
);
```

### (2) SchemeCollectionProvider の追加

BEAR.Sunday では、スキームとホスト名は SchemeCollection というオブジェクトが管理しています。
デフォルトでは、 `bear/vendor/bear/resource/src/Module/SchemeCollectionProvider.php` で SchemeCollection オブジェクトを作ってスキーマとホスト名を登録していますが、このプロバイダは self ホストしか登録してくれません（固定）ので、他のホスト名を使うには新たに SchemeCollection のプロバイダを作って登録します。

SchemeCollectionProvider については、BEAR.Sunday ベースのCMS、[Spout](https://github.com/mackstar/Spout) のコードがとても参考になります。
以下のコードは抜粋ですが、spout のコードのネームスペースを変更してやればそのまま使えます。

```php:spout/src/Provide/Resource/Module/SchemeCollectionProvider.php
// （略）

/**
 * @param string $apps
 *
 * @return void
 *
 * @throws \BEAR\Resource\Exception\AppName
 * @Inject
 * @Named("apps=apps")
 */
public function setApps($apps)
{
    $this->apps = $apps;
}

// （略）

/**
 * Return instance
 *
 * @return SchemeCollection
 */
public function get()
{
    $schemeCollection = new SchemeCollection;
    foreach ($this->apps as $host => $app) {
        $this->setNewScheme($schemeCollection, $host, $app);
    }
    $schemeCollection->scheme('http')->host('*')->toAdapter(new HttpAdapter);
    return $schemeCollection;
}

/**
 *
 */
private function setNewScheme(&$schemeCollection, $host, $app) {
    $pageAdapter = new AppAdapter(
        $this->injector,
        $app['namespace'],
        'Resource\Page',
        $app['path'] . '/Page'
    );
    $appAdapter = new AppAdapter(
        $this->injector,
        $app['namespace'],
        'Resource\App',
        $app['path'] . '/App'
    );
    $schemeCollection->scheme('page')->host($host)->toAdapter($pageAdapter);
    $schemeCollection->scheme('app')->host($host)->toAdapter($appAdapter);
}
```

### (3) SchemeCollectionProvider のバインド

上の SchemeCollectionProvider をバインドするモジュールを作ります。

```php:bear/apps/Myapp.Main/src/Module/ResourceClientModule.php
<?php
namespace Myapp\Main\Module;

use Ray\Di\AbstractModule;
use Ray\Di\Scope;

class ResourceClientModule extends AbstractModule
{
    /**
     * {@inheritdoc}
     */
    protected function configure()
    {
        $this
            ->bind('BEAR\Resource\SchemeCollectionInterface')
            ->toProvider('Myapp\Main\Module\Provider\SchemeCollectionProvider')
            ->in(Scope::SINGLETON);
    }
}
```

### (4) AppModule で上書きインストール
引数に `$this` を付けると上書きになるそうです。これで、元の `bear/vendor/bear/resource/src/Module/SchemeCollectionProvider.php` の代わりに `bear/apps/Myapp.Main/src/Module/Provider/SchemeCollectionProvider.php` が使われるようになります。

```php:bear/apps/Myapp.Main/src/Module/AppModule.php
public funtion configure()
{
// （略）
    $this->install(new \Myapp\Main\Module\ResourceClientModule($this));
}
```

### (5) ホストの設定
以下のように、利用するホストを constants.php などで追加します。
ここに記載した設定は、`SchemeCollectionProvider::setApps()` にて、Named アノテーション `@Named("apps=apps")` を利用してセットされます。

```php:bear/apps/Myapp.Main/var/conf/constants.php
// 略
return [
    'prod' => [
        'apps' => [
            'self' => [
                'namespace' => 'Myapp\\Main',
                'path'=>'/var/www/bear/apps/Myapp.Main/src/Resource'
            ],
            'sub' => [
                'namespace' => 'Myapp\\Sub',
                'path'=>'/var/www/bear/apps/Myapp.Sub/src/Resource'
            ],
        ]
    ],
];
```

## 503 エラー！
これでうまくいくかな、と思ったのですが、503 エラーが出てしまいます。

```
exception 'BEAR\Resource\Exception\Scheme' with message 'app://sub/bar' in bear/vendor/bear/resource/src/Factory.php:63
```

デバッグしてみると、確かに上書きしたプロバイダで SchemeCollection オブジェクトが作られているんですが、もう一個、別ルートで SchemeCollection オブジェクトが作られてしまっており、そちらはオリジナルの処理で（つまりホスト "self" のみが登録されて）作られてしまっています。

原因を突き止めるのに非常に苦労したのですが、結論から言いますと、 EmbedResourceModule でバインドしている Interceptor が邪魔していました。
それは分かったのですが、そもそもこれが何をやっているのか（キャッシュがらみっぽい）、無いとまずいのか、どうやって外すのか、が分かりません ^ ^;;

とりあえず、bear/vendor/bear/resource/src/Module/EmbedResourceModule.php の `bindInterceptor()` を削除すると 503 エラーが無くなり、app://sub/bar を正常に呼び出して動作するようになります。

ただこれだと、直接別ベンダーのコードを書き換えることになるのでよろしくありません。
そこで以下のように、さっきの ResourceClientModule に追記してやれば上書きされるかな、と期待したのですが、駄目みたい。Interceptor は上書きにならないのか、どっかコードに問題があるのか。

```php:bear/apps/Myapp.Main/src/Module/ResourceClientModule.php
<?php
namespace Myapp\Main\Module;

use Ray\Di\AbstractModule;
use Ray\Di\Scope;

class ResourceClientModule extends AbstractModule
{
    /**
     * {@inheritdoc}
     */
    protected function configure()
    {
        $this
            ->bind('BEAR\Resource\SchemeCollectionInterface')
            ->toProvider('Myapp\Main\Module\Provider\SchemeCollectionProvider')
            ->in(Scope::SINGLETON);

        // 代わりの何もしない EmbedInterceptor をバインド。
        $this->bindInterceptor(
            $this->matcher->any(),
            $this->matcher->annotatedWith('BEAR\Resource\Annotation\Embed'),
            [$this->requestInjection('Myapp\Main\Interceptor\EmbedInterceptor')]
        );
    }
}
```
とりあえずここまで。