BEAR.Sundayでコマンドラインだけから利用できるリソースを作る


BEAR.Sunday の[リソース](https://bearsunday.github.io/manuals/1.0/ja/resource.html)は、アプリケーション内のどこからでもURIを通じてアクセス可能です。URIを通じてというのは、ウェブからでもコマンドラインからでも同じリソースにアクセスできるということです。
これはこれで便利ですが、一方でコマンドラインからのみ利用したいような機能があったりします。え、ないですか？聞こえません。

この記事では、BEAR.Sundayでコマンドラインのみから利用できるリソースを作成してみます。

# 最終形

- `src/Resource/Command/` 以下にリソースを作成できる
- `src/Resource/Command/` 以下のリソースがコマンドラインのみから利用できる

以下に順次課題を解決していきましょう。

# 1. `src/Resource/Command/` 以下にリソースを作成できる

BEAR.Sunday のディレクトリ構成のなかで、 `src/Resource/` 以下は特殊な意味を持っています。
リソースは World Wide Web において重要な概念で、アクセス可能な情報源のことを指しますが、BEAR.Sundayでも中心的な概念になっています。 `src/Resource/` 以下の情報がBEAR.Sundayにおける「リソース」です。このリソースには、WWWとおなじように、URI[^1]を通じてアクセスできるようになっています。
Resource ディレクトリ配下の `App` や `Page` はURIにおけるスキーム[^2]にマッピングされています。例えば、src/Resource/App/Hoge.php は内部的に `app://self/hoge` でアクセス可能であり、 src/Resource/Page/Fuga.php は `page://self/fuga` でアクセスできます。

BEAR.Sundayではデフォルトで `app` `page` `http` スキームが登録されています。`http` スキームは src/Resource 以下にないのですが、ウェブからアクセスしたときにデフォルトのスキームが利用されるようになっています(通常は `page` スキーム)。



[^1]: URIは Uniform Resource Identifierの略。基本的にはBEAR.Sundayの「リソース」という概念もここから来ていると思いますが、ここでは議論のスコープから外します。
[^2]: 余談ですが、http などの「スキーム」のスペルは `scheme` で、JsonSchema のスペルは `schema` 、どちらもよく使うBEAR.Sundayでは混乱必定です。

`src/Resource/Command/` にリソースを登録するにはスキームを拡張する必要がありそうです。


## scheme の拡張

カスタムスキームの追加については、以下の記事に詳しく書かれています。

[BEAR.SundayのResourceにカスタムスキームを追加する方法](https://qiita.com/gamu1012/items/7b26422901ca1535bf93)

この記事を参考にしながら `command://` というスキームを作成します。

まずは `SchemeCollectionInterface` を返す Provider を作成します。

```php
<?php
namespace Vendor\Package\Module;

use Ray\Di\ProviderInterface;
use BEAR\Resource\Annotation\AppName;
use BEAR\Resource\AppAdapter;
use Ray\Di\InjectorInterface;
use BEAR\Resource\Module\SchemeCollectionProvider;

final class CommandSchemeCollectionProvider implements ProviderInterface
{
    /**
     * @var string
     */
    private $appName;

    /**
     * @var InjectorInterface
     */
    private $injector;

    /**
     * @var SchemeCollectionProvider
     */
    private $provider;

    /**
     * @param string $appName
     *
     * @AppName("appName")
     */
    public function __construct($appName, InjectorInterface $injector, SchemeCollectionProvider $provider)
    {
        $this->appName = $appName;
        $this->injector = $injector;
        $this->provider = $provider;
    }

    public function get()
    {
        $collection = $this->provider->get();
        $adapter = new AppAdapter($this->injector, $this->appName);
        $collection->scheme('command')->host('self')->toAdapter($adapter);

        return $collection;
    }
}
```

このプロバイダを `SchemeCollectionInterface` に束縛すれば `command://` というスキームが追加されます。`AppModule` などで

```php
$this->bind(SchemaCollectionInterface::class)->toProvider(CommandSchemeCollectionProvider::class);
```

とすれば、 `command://` というスキームが利用可能になります。

# 2. `src/Resource/Command/` 以下のリソースがコマンドラインのみから利用できる

ところで、お題としては「コマンドラインからのみ利用できるスキーム」です。これをこのまま `AppModule` で束縛したらグローバルにスキームが追加され、ウェブからでも利用できてしまいます。どこでどのように束縛すれば良いでしょうか。
そんなときにはアプリケーションコンテキストを利用しましょう。

## アプリケーションコンテキスト

BEAR.Sunday には[アプリケーションコンテキスト](https://qiita.com/koriym/items/d8d14682ce0a542c3b42)という概念があります。

アプリケーションコンテキストはアプリケーションの実態がなにかを決定するものです(一番最初に起動する `$app` オブジェクトの中身)。通常のフレームワークでは prod とか dev とかのコンテキストを渡してアプリケーションオブジェクトを初期化しますが、BEAR.SundayではこれがDecoratorによって表現されています。これによってモジュール[^3]の組み合わせがほとんど無際限に拡がるので、BEAR.Sundayの拡張性のミソになっています。

[^3]: BEAR.Sundayでのモジュールは、DIにおける束縛を決定します。読み込みたいライブラリなどはモジュールで定義します。

`bin/app.php` を見てみましょう。

```php
<?php
require dirname(__DIR__) . '/autoload.php';
exit((require dirname(__DIR__) . '/bootstrap.php')(PHP_SAPI === 'cli' ? 'cli-hal-api-app' : 'hal-api-app'));
```

このハイフンつなぎの文字はそれぞれがコンテキストを表します。イメージとしてはこんなかんじのデコレーションになっています。

```php
$app = new CliModule(new HalModule(new ApiModule(new AppModule))));
```

bootstrap に渡す値は任意です。
`HogeModule` が読み込み可能のとき、 `hoge-app` を bootstrap にわたせば `new HogeModule(new App)` になります。

これを使えば「コマンドラインからのみ利用できる」ような機能が作れそうです。
`bin/command.php` みたいなものを作って、アプリケーション初期化時に `cli-command-app` を渡して、 `CommandModule` を作成してみます。

`CommandModule` はこんな感じです。

```php
<?php
namespace Vendor\Package\Module;

use Ray\Di\AbstractModule;
use BEAR\Sunday\Annotation\DefaultSchemeHost;
use BEAR\Resource\SchemeCollectionInterface;
use BEAR\Resource\Module\SchemeCollectionProvider;

/**
 * CommandSchemeCollectionProvider を利用して command:// スキームを追加
 * src/Resource/Command 以下にリソースを作成できるようにする
 */
class CommandModule extends AbstractModule
{
    public function configure()
    {
        $this->bind(SchemeCollectionProvider::class);
        $this->bind(SchemeCollectionInterface::class)->toProvider(CommandSchemeCollectionProvider::class);
        $this->bind()->annotatedWith(DefaultSchemeHost::class)->toInstance('command://self');
    }
}
```

`annotatedWith(DefaultSchemeHost::class)` をしているのは、BEAR.Sunday内部でURIを通じてリソースにアクセスする際に、ルーティング処理がデフォルトスキームを選択する(通常は `page://` スキーム)ためです。このモジュールが読み込まれるときだけは `command://` スキームをデフォルトにしましょう。

仕上げに、 `bin/command.php` を作ってこのモジュールを読み込むようデコレーションします。

```php
<?php
require dirname(__DIR__) . '/autoload.php';
exit((require dirname(__DIR__) . '/bootstrap.php')('cli-command-app'));
```

これで完成です。

# リソースの作成

あとはリソースを作成するだけです。

```php
<?php
namespace Vendor\Package\Resource\Command;

use BEAR\Resource\ResourceObject;

class Index extends ResourceObject
{
    public function onGet(): ResourceObject
    {
        $this['command'] = 'hogehoge';
        $this['test'] = 'fugafuga';

        return $this;
    }
}
```

これで、次のようにアクセスしてみます。

```shell-session
$ php bin/command.php get /

200 OK
Content-Type: application/json

{
    "command": "hogehoge",
    "test": "fugafuga"
}
```

`bin/app.php` やウェブからだと、CommandModuleが読み込まれていないので、 `src/Resource/Command/` 以下のリソースにはアクセスできません。

# まとめ

BEAR.Sundayにおけるスキームの拡張と、アプリケーションコンテキストを見てきました。このような拡張が必要になるかどうかは状況次第ですが、こういった組み合わせの自由さはフレームワークでみることがあまりないかなと思います。
いろいろな使い方を試してみて、思うように拡張してみるのもBEAR.Sundayというフレームワークの楽しさだと思うので、ぜひ皆さんもいろいろ試してみてください。