2 つ目のドメインエンティティを追加する
このガイドでは、組み込みの Note・Tag 例に倣い、2 つ目のドメインエンティティを追加する方法を説明します。
前提: DB 付きエンドポイントを追加する を完了し、UseCase → Repository → Handler の三層パターンを理解していること。
全体像
2 つ目のエンティティも 1 つ目と同じ構造に従います。重要なのはアプリケーションへの接続方法です:
src/Example/YourEntity/
YourEntity.php ← readonly ドメインオブジェクト
YourEntityRepositoryInterface.php
PdoYourEntityRepository.php
GetYourEntityByIdUseCase.php (+ interface)
GetYourEntityByIdHandler.php
YourEntityRouteRegistrar.php ← __invoke(Router) でルートを登録
YourEntityServiceProvider.php ← DI + ルート登録を配線RuntimeServiceProvider に新しいルート登録クラスと例外ハンドラーを追加するだけです — RuntimeApplicationFactory は変更不要です。
ステップ: Product リソースの例
GET /examples/products/{id} と GET /examples/products を構築します。
1 — ドメインエンティティ
// src/Example/Product/Product.php
final readonly class Product
{
public function __construct(
public readonly int $id,
public readonly string $name,
public readonly int $price,
) {}
}2 — リポジトリインターフェースと PDO アダプター
interface ProductRepositoryInterface
{
public function findById(int $id): Product;
/** @return list<Product> */
public function findAll(int $limit, int $offset): array;
}3 — ユースケースとハンドラー
GetNoteByIdUseCase / GetNoteByIdHandler と同じパターン。
パスパラメーター(例: /products/{id} の id)は Router::PARAMETERS_ATTRIBUTE リクエスト属性から読み取ります。個別の PSR-7 属性としては利用できません。
use Nene2\Routing\Router;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
public function handle(ServerRequestInterface $request): ResponseInterface
{
$params = $request->getAttribute(Router::PARAMETERS_ATTRIBUTE, []);
$id = (int) ($params['id'] ?? 0);
// $id を検証し、ユースケースを呼び出し、レスポンスを返す …
}よくある間違い:
$request->getAttribute('id')は常にnullを返します。必ず$request->getAttribute(Router::PARAMETERS_ATTRIBUTE, [])['id']を使用してください。 詳細は カスタムルートを追加する を参照してください。
4 — ルート登録クラス
RuntimeApplicationFactory にパラメータを追加する代わりに、__invoke 可能なクラスを作成します:
// src/Example/Product/ProductRouteRegistrar.php
final readonly class ProductRouteRegistrar
{
public function __construct(
private GetProductByIdHandler $getHandler,
private ListProductsHandler $listHandler,
) {}
public function __invoke(Router $router): void
{
$getHandler = $this->getHandler;
$listHandler = $this->listHandler;
$router->get('/examples/products', static fn ($r) => $listHandler->handle($r));
$router->get('/examples/products/{id}', static fn ($r) => $getHandler->handle($r));
}
}5 — サービスプロバイダー
// src/Example/Product/ProductServiceProvider.php
final readonly class ProductServiceProvider implements ServiceProviderInterface
{
public function register(ContainerBuilder $builder): void
{
$builder
->set(ProductRepositoryInterface::class, /* ... */)
->set(GetProductByIdHandler::class, /* ... */)
->set(ListProductsHandler::class, /* ... */)
->set(ProductNotFoundExceptionHandler::class, /* ... */)
->set('nene2.route_registrar.product', static function ($c): ProductRouteRegistrar {
return new ProductRouteRegistrar($get, $list);
});
}
}6 — RuntimeServiceProvider に配線する
src/Http/RuntimeServiceProvider.php に 2 箇所追加するだけです:
// 1. プロバイダーを登録(Note・Tag と並べて):
$builder->addProvider(new ProductServiceProvider());
// 2. registrar と例外ハンドラーを RuntimeApplicationFactory の配線に追加:
return new RuntimeApplicationFactory(
$responseFactory, $streamFactory, $logger, $config->machineApiKey,
[$noteNotFoundHandler, $tagNotFoundHandler, $productNotFoundHandler], // ← 追加
$requestIdHolder,
[$noteRegistrar, $tagRegistrar, $productRegistrar], // ← 追加
$bearerMiddleware,
);RuntimeApplicationFactory 自体は変更不要です。
プロバイダーが登録したサービスのオーバーライド
ContainerBuilder::set() は後勝ちです。addProvider() の後に set() を呼ぶと、 プロバイダーがそのキーに登録した値が置き換わります。プロバイダー自体を変更せずに 単一のバインディングを差し替えることができます。
$builder->addProvider(new RuntimeServiceProvider());
// RuntimeApplicationFactory だけ上書き — 他はプロバイダーの設定がそのまま残る
$builder->set(RuntimeApplicationFactory::class, static function ($c) {
return new RuntimeApplicationFactory(/* カスタム配線 */);
});既存の参照例
| エンティティ | ソース | エンドポイント |
|---|---|---|
Note | src/Example/Note/ | GET/POST /examples/notes、GET/PUT/DELETE /examples/notes/{id} |
Tag | src/Example/Tag/ | GET/POST /examples/tags、GET/PUT/DELETE /examples/tags/{id} |
テスト
$registrar = new ProductRouteRegistrar($getHandler, $listHandler);
$application = (new RuntimeApplicationFactory(
$factory, $factory,
domainExceptionHandlers: [new ProductNotFoundExceptionHandler($problemDetails)],
routeRegistrars: [$registrar],
))->create();必要な registrar だけを渡します — 他のエンティティのルートはロードされません。
MCP ツールの追加
エンドポイントが完成したら docs/mcp/tools.json にエントリを追加し、composer mcp で検証してください。write ツールは NENE2_LOCAL_JWT_SECRET の設定が必要です。