REST Controllers
REST controllers in this architecture map HTTP routes to PHP methods using PHP 8 attributes. Every controller method receives HttpRequest and HttpResponse objects and delegates business logic to the Service layer.
Defining Routes with PHP Attributes
Annotate controller classes with zircote/swagger-php attributes to describe each endpoint. The tooling generates public/docs/openapi.json from these annotations, and OpenApiRouteList uses that file to dispatch requests at runtime.
namespace RestReferenceArchitecture\Rest;
use ByJG\RestServer\HttpRequest;
use ByJG\RestServer\HttpResponse;
use OpenApi\Attributes as OA;
class Login
{
/**
* Do log in
*/
#[OA\Post(
path: "/login",
tags: ["Login"],
)]
#[OA\RequestBody(
description: "The Login Data",
required: true,
content: new OA\JsonContent(
required: [ "username", "password" ],
properties: [
new OA\Property(property: "username", description: "The Username", type: "string", format: "string"),
new OA\Property(property: "password", description: "The Password", type: "string", format: "string")
]
)
)]
#[OA\Response(
response: 200,
description: "Login result",
content: new OA\JsonContent(
required: [ "token" ],
properties: [
new OA\Property(property: "token", type: "string"),
new OA\Property(property: "data", properties: [
new OA\Property(property: "userid", type: "string"),
new OA\Property(property: "name", type: "string"),
new OA\Property(property: "role", type: "string"),
])
]
)
)]
public function mymethod(HttpRequest $request, HttpResponse $response): void
{
// ...
}
}
Path and Query Parameters
Declare path parameters directly in the path string and use #[OA\Parameter] to describe them:
#[OA\Get(path: "/dummy/{id}", tags: ["Dummy"])]
#[OA\Parameter(name: "id", in: "path", required: true, schema: new OA\Schema(type: "integer"))]
#[OA\Parameter(name: "page", in: "query", required: false, schema: new OA\Schema(type: "integer"))]
#[OA\Response(response: 200, description: "The dummy object")]
public function getDummy(HttpResponse $response, HttpRequest $request): void
{
$id = $request->param('id');
$page = $request->get('page');
// ...
}
Request Bodies
Use #[OA\RequestBody] with #[OA\JsonContent] to define the expected payload shape:
#[OA\Post(path: "/dummy", tags: ["Dummy"])]
#[OA\RequestBody(
required: true,
content: new OA\JsonContent(ref: "#/components/schemas/DummyBody")
)]
#[OA\Response(response: 200, description: "Created dummy")]
public function postDummy(HttpResponse $response, HttpRequest $request): void
{
// ...
}
Response Schemas
Document every response status code so the OpenAPI spec (and test validation) stays accurate:
#[OA\Response(
response: 200,
description: "Success",
content: new OA\JsonContent(ref: "#/components/schemas/Dummy")
)]
#[OA\Response(response: 404, description: "Not found")]
#[OA\Response(response: 422, description: "Validation error")]
Request Validation with #[ValidateRequest]
Add #[ValidateRequest] to a controller method to automatically validate the incoming request body against the OpenAPI schema before the method executes. Invalid payloads receive a 422 response.
use RestReferenceArchitecture\Attributes\ValidateRequest;
#[ValidateRequest]
public function postDummy(HttpResponse $response, HttpRequest $request): void
{
$payload = ValidateRequest::getPayload();
$service = Config::get(DummyService::class);
$model = $service->create($payload);
$response->write($model);
}
Requiring Authentication with #[RequireAuthenticated]
Add #[RequireAuthenticated] to protect an endpoint. Requests without a valid JWT receive a 401 response.
use RestReferenceArchitecture\Attributes\RequireAuthenticated;
#[RequireAuthenticated]
public function getDummy(HttpResponse $response, HttpRequest $request): void
{
$service = Config::get(DummyService::class);
$result = $service->getOrFail($request->param('id'));
$response->write($result);
}
Getting Services via Config::get()
Retrieve a DI-registered service inside a controller method:
use ByJG\Config\Config;
use RestReferenceArchitecture\Service\DummyService;
public function getDummy(HttpResponse $response, HttpRequest $request): void
{
$service = Config::get(DummyService::class);
$result = $service->getOrFail($request->param('id'));
$response->write($result);
}
Working with HttpRequest and HttpResponse
// Path parameter (from the URL pattern, e.g. /dummy/{id})
$id = $request->param('id');
// Query string parameter (from ?page=2)
$page = $request->get('page');
// Request body as string
$rawBody = $request->payload();
// Write a model or array to the response (serialized to JSON)
$response->write($model);
// Write a scalar
$response->write(['result' => 'ok']);
Using an Existing OpenAPI Specification
If you already have an OpenAPI JSON spec, place it at public/docs/openapi.json. Set the operationId for each path to route requests to the correct controller method:
{
"paths": {
"/login": {
"post": {
"operationId": "POST::/login::RestReferenceArchitecture\\Rest\\Login::mymethod"
}
}
}
}
The operationId format is:
<HTTP Method>::<path>::<Fully\Qualified\ClassName>::<methodName>
Related Documentation
- OpenAPI Integration - How the spec is generated and used at runtime
- Attributes Reference - Full attribute reference
- Authentication - JWT and RBAC setup
- Service Layer - Keeping controllers thin