FrameworkTarget::Laravel generates framework-aware code optimized for Laravel applications:
generated/
├── Api/
│ ├── PetsApiInterface.php ← Interface defining all operations
│ ├── PetsApiController.php ← Abstract controller
│ └── PetsApiRoutes.php ← Route registration helper
└── Model/
├── Pet.php ← Full-featured models with serialization
├── NewPet.php
├── PetStatus.php ← Enums for type constraints
└── ApiError.php
The generated controller integrates with Laravel’s request/response handling:
namespace App\Api;
use App\Model\NewPet;
use App\Model\Pet;
abstract class PetsApiController implements PetsApiInterface
{
public function listPetsAction(\Illuminate\Http\Request $request): \Illuminate\Http\JsonResponse
{
$result = $this->listPets(
limit: $this->parseParameter($this->queryParameterValues($request, 'limit'), 'limit', 'int', false),
);
return response()->json($result, 200);
}
public function createPetAction(\Illuminate\Http\Request $request): \Illuminate\Http\JsonResponse
{
$body = $this->parseJsonRequestBody($request, static fn(array $data): NewPet => NewPet::fromRequestArray($data));
$result = $this->createPet(
body: $body,
);
return response()->json($result->toResponseArray(), 201);
}
public function showPetByIdAction(\Illuminate\Http\Request $request): \Illuminate\Http\JsonResponse
{
$result = $this->showPetById(
petId: $this->parseParameter($request->route('petId'), 'petId', 'int', true),
);
return response()->json($result->toArray(), 200);
}
// ... other operations
// Abstract domain methods — you implement these
abstract public function listPets(?int $limit): array;
abstract public function createPet(NewPet $body): Pet;
// ...
}
Easily register all routes for an API group:
namespace App\Api;
use Illuminate\Support\Facades\Route;
final class PetsApiRoutes
{
private function __construct()
{
}
public static function register(string $controller = PetsApiController::class): void
{
Route::get('/pets', [$controller, 'listPetsAction']);
Route::post('/pets', [$controller, 'createPetAction']);
Route::get('/pets/{petId}', [$controller, 'showPetByIdAction']);
Route::put('/pets/{petId}', [$controller, 'upsertPetAction']);
Route::delete('/pets/{petId}', [$controller, 'deletePetAction']);
}
}
Generated actions convert path, query, header and cookie values to the declared
PHP types (int, float, bool, enums, DateTimeImmutable for date /
date-time, and arrays following the OpenAPI style/explode rules) through
the protected parseParameter() / queryParameterValues() helpers. Missing
required or invalid values throw a BadRequestHttpException (HTTP 400).
JSON request bodies are read by the protected parseJsonRequestBody() helper:
a missing required body, invalid JSON, a non-object body or a body that cannot
be hydrated into the DTO also throws a BadRequestHttpException (HTTP 400);
optional bodies are passed as null when the request has no content. With
validateServerRequest enabled, failed request validation throws an
UnprocessableEntityHttpException (HTTP 422) for NativeMethod and
SymfonyConstraints, and Laravel’s ValidationException (HTTP 422 with the
usual errors structure) for LaravelValidation. Response validation failures
stay an InvalidArgumentException (HTTP 500) because they indicate a server
bug. Operations whose domain method is not overridden throw a
BadMethodCallException.
Union return types respond with the status code of the returned model, and
scalar results are returned as JSON unchanged.
Extend the abstract controller and implement the domain methods:
<?php
declare(strict_types=1);
namespace App\Http\Controllers\Api;
use App\Api\PetsApiController;
use App\Model\NewPet;
use App\Model\Pet;
use App\Repository\PetRepository;
use Illuminate\Http\Request;
use Illuminate\Http\JsonResponse;
final class PetController extends PetsApiController
{
public function __construct(private readonly PetRepository $repository)
{
}
// PATTERN 1: Implement domain method (simple case, 95% of operations)
public function createPet(NewPet $body): Pet
{
return $this->repository->create($body->name, $body->tag);
}
public function listPets(?int $limit): array
{
return $this->repository->findAll($limit);
}
public function showPetById(string $petId): Pet
{
$pet = $this->repository->find((int) $petId);
if ($pet === null) {
abort(404, "Pet $petId not found");
}
return $pet;
}
// PATTERN 2: Override *Action method for full HTTP control (edge case)
#[\Override]
public function upsertPetAction(Request $request): JsonResponse
{
// Custom logic with conditional status code
$petId = $request->route('petId');
$body = NewPet::fromArray($request->all());
$existing = $this->repository->find((int) $petId);
if ($existing !== null) {
return response()->json(
$this->repository->update($existing, $body->tag)->toArray(),
200 // Update
);
}
return response()->json(
$this->repository->create($body->name, $body->tag)->toArray(),
201 // Create
);
}
// ... implement other domain methods ...
public function deletePet(string $petId): void
{
$pet = $this->repository->find((int) $petId);
if ($pet === null) {
abort(404, "Pet $petId not found");
}
$this->repository->delete($pet);
}
}
In routes/api.php:
<?php
use App\Api\PetsApiRoutes;
use App\Http\Controllers\Api\PetController;
use Illuminate\Support\Facades\Route;
Route::middleware('api')->group(function () {
PetsApiRoutes::register(PetController::class);
});
<?php
use App\Http\Controllers\Api\PetController;
use Illuminate\Support\Facades\Route;
Route::middleware('api')->group(function () {
Route::get('/pets', [PetController::class, 'listPetsAction']);
Route::post('/pets', [PetController::class, 'createPetAction']);
Route::get('/pets/{petId}', [PetController::class, 'showPetByIdAction']);
Route::put('/pets/{petId}', [PetController::class, 'upsertPetAction']);
Route::delete('/pets/{petId}', [PetController::class, 'deletePetAction']);
});
Use Laravel’s service container for automatic injection:
final class PetController extends PetsApiController
{
public function __construct(
private readonly PetRepository $repository,
private readonly Logger $logger,
) {}
}
Use abort() or exception handling with Laravel’s exception handler:
public function createPet(NewPet $body): Pet
{
if (empty($body->name)) {
abort(400, 'Name is required');
}
if ($this->repository->nameExists($body->name)) {
abort(422, 'Pet name already exists');
}
return $this->repository->create($body->name, $body->tag);
}
Override the *Action method for custom headers:
#[\Override]
public function listPetsAction(Request $request): JsonResponse
{
$result = $this->listPets(
limit: $this->parseParameter($this->queryParameterValues($request, 'limit'), 'limit', 'int', false),
);
return response()
->json($result, 200)
->header('X-Total-Count', count($result))
->header('Cache-Control', 'max-age=3600');
}
use Illuminate\Support\Facades\Validator;
public function createPet(NewPet $body): Pet
{
// Validation happens before reaching domain method
// But you can add defensive checks:
if (!$this->isValidName($body->name)) {
abort(400, 'Invalid pet name');
}
return $this->repository->create($body->name, $body->tag);
}
Leverage Laravel’s event system:
use App\Events\PetCreated;
public function createPet(NewPet $body): Pet
{
$pet = $this->repository->create($body->name, $body->tag);
event(new PetCreated($pet));
return $pet;
}
use Illuminate\Support\Facades\DB;
public function createPet(NewPet $body): Pet
{
return DB::transaction(function () use ($body) {
return $this->repository->create($body->name, $body->tag);
});
}
*Action methods only when HTTP behavior conflicts with OpenAPI specabort() or throw exceptions for error casesuse Tests\TestCase;
class PetControllerTest extends TestCase
{
public function test_list_pets(): void
{
$response = $this->getJson('/api/pets?limit=10');
$response->assertStatus(200)
->assertJsonIsArray();
}
public function test_create_pet_without_name(): void
{
$response = $this->postJson('/api/pets', ['tag' => 'fluffy']);
$response->assertStatus(400);
}
public function test_delete_nonexistent_pet(): void
{
$response = $this->deleteJson('/api/pets/999');
$response->assertStatus(404);
}
}
Serve your OpenAPI spec and use:
In routes/web.php:
Route::get('/docs', function () {
return view('swagger', [
'spec' => asset('api/openapi.yaml'),
]);
});