はじめに
BEAR.Sunday Advent Calendar 2020 で以下の記事を投稿しました。
上記記事中において Front & Backend
のケースとして他の PHP アプリケーションから BEAR.Sunday を利用する以下の記事を紹介しました。
今回は Ray.Di for Laravel を使い、既存の Ray.Di アプリケーションから BEAR.Sunday アプリケーションを利用する例を紹介します。
実現すること
以下のようなことを実現します。
- Ray.Di for Laravel を適用した Laravel アプリケーションから BEAR アプリケーションの Resource を利用できる
- 上記で利用できるようにしたものとは別の BEAR アプリケーションの Resource を利用できる
リポジトリ
本記事で紹介するコードは下記リポジトリにあるものです。
BEARアプリケーションを実装する
まずは BEAR アプリケーションを実装します。
BEAR アプリケーションといってもルーターなどは不要なので、 BEAR.Resource のみを依存に追加します。
"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 をインストールします。
<?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 オブジェクトに変換します。
<?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);
}
}
モジュールをインストールします。
protected function configure(): void
{
$this->install(new \NaokiTsuchiya\MyApp\AppModule());
}
次のような BEAR アプリケーションを呼び出す部分まで含めたテストを実装できます。
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 を利用します。
ここでは、other
というホスト名で other-bear-app
のリソースをインポートします。
/** @inheritDoc */
protected function configure()
{
+ $this->install(
+ new ImportAppModule(
+ [new ImportApp('other', 'NaokiTsuchiya\OtherApp', '')]
+ )
+ );
$this->install(new ResourceModule(__NAMESPACE__));
}
}
インポートしたアプリケーションのリソースを直接 Laravel アプリケーションから呼び出すことが可能ですが、ここでは既存のアプリケーションのみを呼び出すことにしましょう。 以下の Greeting
は other-bear-app
のリソースを呼び出します。
<?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 を呼び出すコントローラを実装します。
<?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);
}
}
テストも同様に実装します。
<?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
上記のような煩わしさから開放されるには、依存として使われるアプリケーション側がパッケージとして利用されることを前提とした識別子を使った束縛をすることや、Injector を複数持つような構成を検討しましょう。2
カスタムホストとして別の Injector を利用したい場合は、以下が参考になるかもしれません。