# Laravel API — プロジェクト CLAUDE.md > PostgreSQL、Redis、キューを使用したLaravel APIの実世界サンプル。 > これをプロジェクトのルートにコピーしてサービスに合わせてカスタマイズしてください。 ## プロジェクト概要 **スタック:** PHP 8.2+, Laravel 11.x, PostgreSQL, Redis, Horizon, PHPUnit/Pest, Docker Compose **アーキテクチャ:** コントローラー → サービス → アクションのモジュール型Laravelアプリ、Eloquent ORM、非同期処理のためのキュー、バリデーションのためのForm Request、一貫したJSONレスポンスのためのAPI Resource。 ## 重要なルール ### PHP の規約 - すべてのPHPファイルに `declare(strict_types=1)` を記述する - 型付きプロパティと戻り値の型をあらゆる場所で使用する - サービスとアクションには `final` クラスを優先する - コミット済みコードに `dd()` や `dump()` を使用しない - Laravel Pint(PSR-12)でフォーマットする ### APIレスポンスエンベロープ すべてのAPIレスポンスは一貫したエンベロープを使用します: ```json { "success": true, "data": {"...": "..."}, "error": null, "meta": {"page": 1, "per_page": 25, "total": 120} } ``` ### データベース - マイグレーションはgitにコミットする - EloquentまたはクエリビルダーをSQLクエリに使用する(パラメータ化されていない生SQLは禁止) - `where` または `orderBy` で使用されるカラムにインデックスを付ける - サービス内でモデルインスタンスの変更を避ける。リポジトリまたはクエリビルダーを通じた作成/更新を優先する ### 認証 - SanctumによるAPI認証 - モデルレベルの認可にはポリシーを使用する - コントローラーとサービスで認証を強制する ### バリデーション - バリデーションにはForm Requestを使用する - ビジネスロジック用にDTOへ入力を変換する - 派生フィールドに対してリクエストペイロードを信頼しない ### エラーハンドリング - サービスでドメイン例外をスローする - `bootstrap/app.php` の `withExceptions` で例外をHTTPレスポンスにマップする - 内部エラーをクライアントに公開しない ### コードスタイル - コードやコメントに絵文字を使用しない - 最大行長: 120文字 - コントローラーは薄く。サービスとアクションがビジネスロジックを保持する ## ファイル構成 ``` app/ Actions/ Console/ Events/ Exceptions/ Http/ Controllers/ Middleware/ Requests/ Resources/ Jobs/ Models/ Policies/ Providers/ Services/ Support/ config/ database/ factories/ migrations/ seeders/ routes/ api.php web.php ``` ## 主要なパターン ### サービスレイヤー ```php orders->create($data); } } final class OrderService { public function __construct(private CreateOrderAction $createOrder) {} public function placeOrder(CreateOrderData $data): Order { return $this->createOrder->handle($data); } } ``` ### コントローラーパターン ```php service->placeOrder($request->toDto()); return response()->json([ 'success' => true, 'data' => OrderResource::make($order), 'error' => null, 'meta' => null, ], 201); } } ``` ### ポリシーパターン ```php user_id === $user->id; } } ``` ### Form Request + DTO ```php user(); } public function rules(): array { return [ 'items' => ['required', 'array', 'min:1'], 'items.*.sku' => ['required', 'string'], 'items.*.quantity' => ['required', 'integer', 'min:1'], ]; } public function toDto(): CreateOrderData { return new CreateOrderData( userId: (int) $this->user()->id, items: $this->validated('items'), ); } } ``` ### APIリソース ```php $this->id, 'status' => $this->status, 'total' => $this->total, 'created_at' => $this->created_at?->toIso8601String(), ]; } } ``` ### キュージョブ ```php findOrFail($this->orderId); $mailer->sendOrderConfirmation($order); } } ``` ### テストパターン(Pest) ```php create(); actingAs($user); $response = postJson('/api/orders', [ 'items' => [['sku' => 'sku-1', 'quantity' => 2]], ]); $response->assertCreated(); assertDatabaseHas('orders', ['user_id' => $user->id]); }); ``` ### テストパターン(PHPUnit) ```php create(); $response = $this->actingAs($user)->postJson('/api/orders', [ 'items' => [['sku' => 'sku-1', 'quantity' => 2]], ]); $response->assertCreated(); $this->assertDatabaseHas('orders', ['user_id' => $user->id]); } } ```