Ray.Di アプリケーションから BEAR.Sunday を呼び出す

# はじめに

[BEAR.Sunday Advent Calendar 2020](https://qiita.com/advent-calendar/2020/bear-sunday) で以下の記事を投稿しました。

https://qiita.com/NaokiTsuchiya/items/b4df3e880a9265ed4aba

上記記事中において `Front & Backend` のケースとして他の PHP アプリケーションから BEAR.Sunday を利用する以下の記事を紹介しました。

https://qiita.com/koriym/items/9bf5ed9dcb1441ce5e89

今回は [Ray.Di for Laravel](https://packagist.org/packages/ray/ray-di-for-laravel) を使い、既存の Ray.Di アプリケーションから BEAR.Sunday アプリケーションを利用する例を紹介します。

# 実現すること

以下のようなことを実現します。

* Ray.Di for Laravel を適用した Laravel アプリケーションから BEAR アプリケーションの Resource を利用できる
* 上記で利用できるようにしたものとは別の BEAR アプリケーションの Resource を利用できる


# リポジトリ

本記事で紹介するコードは下記リポジトリにあるものです。

https://github.com/NaokiTsuchiya/BEAR.Sunday-Advent-Calendar-2022

# BEARアプリケーションを実装する

まずは BEAR アプリケーションを実装します。
BEAR アプリケーションといってもルーターなどは不要なので、 BEAR.Resource のみを依存に追加します。

```diff
     "license": "MIT",
     "require": {
         "php": "^8.0.2",
+        "bear/resource": "^1.18",
         "guzzlehttp/guzzle": "^7.2",
         "laravel/framework": "^9.19",
         "laravel/sanctum": "^3.0",
```

今回は以下のような `bear-app` というディレクトリを作成し、この中に独立したアプリケーションとして実装します。
既存の Laravel アプリケーションに影響を与えることがないので、導入が簡単です。

```
./bear-app
├── src
│   ├── AppModule.php
│   └── Resource
│       └── Page
│           └── Index.php
└── tests
    └── Resource
        └── Page
            └── IndexTest.php
```

AppModule では ResourceModule をインストールします。

```bear-app/src/AppModule.php
<?php

declare(strict_types=1);

namespace NaokiTsuchiya\MyApp;

use BEAR\Resource\Module\ResourceModule;
use Ray\Di\AbstractModule;

class AppModule extends AbstractModule
{
    /** @inheritDoc */
    protected function configure()
    {
        $this->install(new ResourceModule(__NAMESPACE__));
    }
}
```

この時点で BEAR アプリケーション内でのテストができます。
必要に応じて Workflow テストなども実装しましょう。

# Laravel アプリケーションから Resource を呼び出す

コントローラの依存に ResourceInterface を追加し、作成したリソースへリクエストした結果を Responce オブジェクトに変換します。

```app/Http/Controllers/IndexController.php
<?php

declare(strict_types=1);

namespace App\Http\Controllers;

use BEAR\Resource\ResourceInterface;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Ray\RayDiForLaravel\Attribute\Injectable;

#[Injectable]
class IndexController extends Controller
{
    public function __construct(private readonly ResourceInterface $resource)
    {
    }

    public function __invoke(Request $request): Response
    {
        $name = $request->get('name', 'World');
        $ro = $this->resource->get('page://self/index', ['name' => $name]);

        return new Response((string) $ro, $ro->code, $ro->headers);
    }
}
```

モジュールをインストールします。

```app/RayDi/AppModule.php
    protected function configure(): void
    {
        $this->install(new \NaokiTsuchiya\MyApp\AppModule());
    }
```

次のような BEAR アプリケーションを呼び出す部分まで含めたテストを実装できます。

```tests/Feature/ExampleTest.php
    public function test_the_application_returns_a_successful_response()
    {
        $response = $this->get('/?name=Test');

        $response->assertStatus(200);
        $response->assertJson(['greeting' => 'Hello Test'], true);
        $response->assertHeader('content-type', 'application/json');
    }
```

ここまでで `Laravel アプリケーションから BEAR アプリケーションの Resource を利用できる` が実現できました。

# 別の BEAR アプリケーションを追加する

## もう 1 つの BEAR アプリケーションを実装する

もう1つ別の BEAR アプリケーションを追加してみましょう。

`other-bear-app` ディレクトリに先程と同様に BEAR アプリケーションを実装します。

```
./other-bear-app
├── src
│   ├── AppModule.php
│   └── Resource
│       └── Page
│           └── Index.php
└── tests
    └── Resource
        └── IndexTest.php
```

## アプリケーションをインポートする

`bear-app` のアプリケーションに `other-bear-app` のアプリケーションをインポートするために [ImportAppModule](https://github.com/bearsunday/BEAR.Resource/blob/37d80c586917d0b3be57cd87b97eb0217f457dee/src/Module/ImportAppModule.php) を利用します。

ここでは、`other` というホスト名で `other-bear-app` のリソースをインポートします。

```bear-app/src/AppModule.php.diff
     /** @inheritDoc */
     protected function configure()
     {
+        $this->install(
+            new ImportAppModule(
+                [new ImportApp('other', 'NaokiTsuchiya\OtherApp', '')]
+            )
+        );
         $this->install(new ResourceModule(__NAMESPACE__));
     }
 }
```

インポートしたアプリケーションのリソースを直接 Laravel アプリケーションから呼び出すことが可能ですが、ここでは既存のアプリケーションのみを呼び出すことにしましょう。 以下の `Greeting` は `other-bear-app` のリソースを呼び出します。

```bear-app/src/Resource/Page/Greeting.php
<?php

declare(strict_types=1);

namespace NaokiTsuchiya\MyApp\Resource\Page;

use BEAR\Resource\ResourceInterface;
use BEAR\Resource\ResourceObject;

class Greeting extends ResourceObject
{
    public function __construct(private readonly ResourceInterface $resource)
    {
    }

    public function onGet(string $name = 'other'): static
    {
        $ro = $this->resource->get('page://other/', ['name' => $name]);
        $this->headers = $ro->headers;
        $this->body = $ro->body;

        return $this;
    }
}
```

Greeting を呼び出すコントローラを実装します。

```app/Http/Controllers/GreetingController.php
<?php

declare(strict_types=1);

namespace App\Http\Controllers;

use BEAR\Resource\ResourceInterface;
use Illuminate\Http\Response;
use Ray\RayDiForLaravel\Attribute\Injectable;

#[Injectable]
class GreetingController extends Controller
{
    public function __construct(private readonly ResourceInterface $resource)
    {
    }

    public function __invoke(): Response
    {
        $ro = $this->resource->get('page://self/greeting');

        return new Response((string) $ro, $ro->code, $ro->headers);
    }
}
```

テストも同様に実装します。

```tests/Feature/GreetingTest.php
<?php

namespace Tests\Feature;

use Tests\TestCase;

class GreetingTest extends TestCase
{
    /**
     * @return void
     */
    public function test_the_application_returns_a_successful_response()
    {
        $response = $this->get('/greeting');

        $response->assertStatus(200);
        $response->assertJson(['greeting' => 'Hello other'], true);
        $response->assertHeader('content-type', 'application/json');
    }
}
```

これで `別の BEAR アプリケーションの Resource を利用できる` が実現されました。



# おわりに

かなり駆け足でしたが、当初実現しようとしていた以下の2点を実現することができました。

* Ray.Di for Laravel を適用した Laravel アプリケーションから BEAR アプリケーションの Resource を利用できる
* 上記で利用できるようにしたものとは別の BEAR アプリケーションの Resource を利用できる

今回の例では、単一のリポジトリ内に複数のアプリケーションを持つようにしましたが、別のリポジトリに切り出し、 Composer の依存として利用することもできます。

注意点として、今回実装した方式では 1 つの Injector によって複数のアプリケーションを跨ぐ巨大なオブジェクトグラフを構築している点が挙げられます。
利用側の束縛のルールによっては意図せず束縛が変わってしまう場合がある点に注意しましょう。[^1]
[^1]: PSR の LoggerInterface に Laravel のロガーが束縛されてしまうなど

上記のような煩わしさから開放されるには、依存として使われるアプリケーション側がパッケージとして利用されることを前提とした識別子を使った束縛をすることや、Injector を複数持つような構成を検討しましょう。[^2]
[^2]:環境変数も気をつける必要があるので重複を避けることを前提とするのが良いかもしれません。

カスタムホストとして別の Injector を利用したい場合は、以下が参考になるかもしれません。

https://qiita.com/jingu/items/61beb6f1eb121b4638f3