HTTP Request Mapping with Winter Boot PHP 8 Attributes
Winter Boot routes requests with PHP 8 attributes. Instead of a central routes file, you put mapping attributes on controller methods — and optionally on the controller class to set a base path. The framework reads them at startup, builds the routing table, and calls the matching handler with its parameters already bound and converted to the declared types.
#[RequestMapping]
Section titled “#[RequestMapping]”#[RequestMapping] (namespace dev\winterframework\stereotype\web\RequestMapping) can be applied at class level to set a base URI prefix, or at method level to define the full route for that handler. When both are present, the class-level path is prepended to the method-level path automatically.
-
pathstring|arrayThe URI pattern for this mapping. Supports{variable}placeholders for path variables. Accepts a single string or an array of strings to map multiple paths to the same handler. Literal segments may contain letters, digits,_,-and., so file-like routes such as/robots.txtor/assets/app.min.jswork; a segment made only of dots (.,..) is rejected at startup. A literal segment takes priority over a{variable}at the same position. -
methodarrayList ofRequestMethodconstants this handler accepts — for example,[RequestMethod::GET, RequestMethod::POST]. Defaults to all HTTP methods when omitted. -
namestringAn optional name for this mapping, shown in logs and the actuator’s mappings endpoint. -
consumesarrayMedia types this handler accepts — for example,['application/json']. Requests with any otherContent-Typeare rejected with400 Bad Request. -
producesarrayMedia types this handler returns — for example,['application/json']. Informational only: it is listed in the actuator’s mappings endpoint but does not set or negotiate the responseContent-Type.
RequestMethod Enum Values
Section titled “RequestMethod Enum Values”RequestMethod (namespace dev\winterframework\enums\RequestMethod) defines constants for every HTTP method the framework accepts in the method array.
| Constant | HTTP Method |
|---|---|
RequestMethod::GET |
GET |
RequestMethod::HEAD |
HEAD |
RequestMethod::POST |
POST |
RequestMethod::PUT |
PUT |
RequestMethod::PATCH |
PATCH |
RequestMethod::DELETE |
DELETE |
RequestMethod::OPTIONS |
OPTIONS |
RequestMethod::TRACE |
TRACE |
Class-level and Method-level Paths Combined
Section titled “Class-level and Method-level Paths Combined”<?php
use dev\winterframework\stereotype\RestController;use dev\winterframework\stereotype\web\RequestMapping;use dev\winterframework\enums\RequestMethod;
#[RestController]#[RequestMapping(path: '/api/v1')] // base prefix for every method belowclass ProductController{ // Handled at GET /api/v1/products #[RequestMapping(path: '/products', method: [RequestMethod::GET])] public function listProducts(): array { return []; }
// Handled at POST /api/v1/products #[RequestMapping(path: '/products', method: [RequestMethod::POST])] public function createProduct(): array { return []; }}When the class-level path is an array, every class path prefixes every method path: #[RequestMapping(path: ['/v1', '/v2'])] with #[GetMapping(path: '/items')] maps both /v1/items and /v2/items. An empty class path ('' or '/') maps the method path without a prefix.
HTTP Method Shortcuts
Section titled “HTTP Method Shortcuts”Winter Boot ships five convenience attributes that combine #[RequestMapping] with a fixed HTTP method. They accept the same path, name, consumes, and produces parameters, but you never need to specify method explicitly.
| Attribute | HTTP method |
|---|---|
#[GetMapping] |
GET |
#[PostMapping] |
POST |
#[PutMapping] |
PUT |
#[PatchMapping] |
PATCH |
#[DeleteMapping] |
DELETE |
use dev\winterframework\stereotype\web\GetMapping;
#[GetMapping(path: '/users')]public function listUsers(): array{ return $this->userService->findAll();}#[RequestParam]
Section titled “#[RequestParam]”#[RequestParam] (namespace dev\winterframework\stereotype\web\RequestParam) binds a query-string value, POST field, cookie, or HTTP header to a method parameter. Apply it at the parameter level.
-
namestringName of the incoming parameter. Defaults to the PHP variable name when omitted. -
requiredboolWhether the parameter must be present in the request. Defaults totrue— a missing value returns400 Bad Request, even if the PHP parameter has a default. Setrequired: falsefor optional parameters. -
defaultValuemixedValue used when the parameter is absent andrequiredisfalse. A PHP default on the parameter (string $name = 'World') works too. -
sourcestringWhere to read the value from. Defaults to'request'. See the source table below.
Source Values
Section titled “Source Values”| Value | Reads from |
|---|---|
request |
URL query string or POST body (checked in that order) |
get |
URL query string only ($_GET) |
post |
POST url-encoded / form body only ($_POST) |
cookie |
HTTP cookie ($_COOKIE) |
header |
HTTP request header |
use dev\winterframework\stereotype\web\GetMapping;use dev\winterframework\stereotype\web\PostMapping;use dev\winterframework\stereotype\web\RequestParam;
// Query-string: GET /divide?a=10&b=2#[GetMapping(path: '/divide')]public function divide( #[RequestParam] int $a, #[RequestParam] int $b,): float { return $a / $b;}
// Optional parameter with a default value#[GetMapping(path: '/greet')]public function greet( #[RequestParam(required: false, defaultValue: 'World')] string $name,): string { return 'Hello, ' . $name;}
// Read from a specific HTTP header#[PostMapping(path: '/orders')]public function createOrder( #[RequestParam(name: 'X-Tenant-Id', source: 'header')] string $tenantId,): array { return $this->orderService->create($tenantId);}
// Read from a cookie#[GetMapping(path: '/profile')]public function profile( #[RequestParam(name: 'session_token', source: 'cookie')] string $token,): array { return $this->sessionService->getProfile($token);}#[PathVariable]
Section titled “#[PathVariable]”#[PathVariable] (namespace dev\winterframework\stereotype\web\PathVariable) binds a URI template segment — the {placeholder} in the path string — to a method parameter. The placeholder name must match the PHP variable name, or you can specify it explicitly via the name option. Supported scalar types are string, int, float, and bool.
use dev\winterframework\stereotype\web\GetMapping;use dev\winterframework\stereotype\web\PathVariable;
// GET /hello/Alice → "Hello, Alice"#[GetMapping(path: '/hello/{name}')]public function sayHello(#[PathVariable] string $name): string{ return 'Hello, ' . $name;}
// GET /users/42 → finds user with id 42#[GetMapping(path: '/users/{id}')]public function getUser(#[PathVariable] int $id): array{ return $this->userService->findById($id);}#[RequestBody]
Section titled “#[RequestBody]”#[RequestBody] (namespace dev\winterframework\stereotype\web\RequestBody) binds the entire HTTP request body to a method parameter. Winter Boot maps JSON, XML, URL-encoded form, and multipart bodies onto the target class automatically. The parameter must be type-hinted with a concrete class, or string to receive the raw body. Union types are not supported.
use dev\winterframework\stereotype\web\PostMapping;use dev\winterframework\stereotype\web\RequestBody;
// Accepts both JSON and url-encoded bodies:// {"a": 10, "b": 20} or a=10&b=20#[PostMapping(path: '/calc/add')]public function add(#[RequestBody] AddRequest $request): int{ return $this->calcService->add($request->a, $request->b);}class AddRequest{ public int $a; public int $b;}#[PostMapping(path: '/webhooks/ingest')]public function ingest(#[RequestBody] string $rawPayload): array{ $data = json_decode($rawPayload, true); return $this->webhookService->handle($data);}Single-string-arg constructor fallback
Section titled “Single-string-arg constructor fallback”When the request Content-Type is none of form, multipart, JSON, or XML — or when parsing is disabled (see disableParsing below) — the raw body string is passed straight to your class constructor. The class must therefore declare a constructor taking a single string argument:
class CsvPayload{ public array $rows;
public function __construct(string $raw) { $this->rows = explode("\n", trim($raw)); }}Any other constructor shape fails here and the request is rejected with 400 Bad Request.
disableParsingboolSkips JSON/XML parsing. Defaults tofalse. Whentrue, the raw body is handed to the single-string-arg constructor instead — useful for hand-rolled formats.
use dev\winterframework\stereotype\web\PostMapping;use dev\winterframework\stereotype\web\RequestBody;
#[PostMapping(path: '/import')]public function import(#[RequestBody(disableParsing: true)] CsvPayload $payload): array{ return $this->importService->handle($payload->rows);}File uploads (multipart/form-data)
Section titled “File uploads (multipart/form-data)”For multipart/form-data requests, text fields and uploaded files are merged into a single map keyed by field name, then bound to your DTO by property name:
- Declare text fields as scalars (
string,int,float,bool) — values are type-cast. - Declare a file field as
HttpUploadedFile(namespacedev\winterframework\web\http\HttpUploadedFile) to get a typed object —getFilePath()for the temp path, plusgetName(),getSize(),getError()/getErrorText(). Declare it asarrayinstead to receive the raw$_FILESentry (name,type,tmp_name,error,size). - A multi-file field (
<input type="file" name="photos" multiple>) keeps PHP’s parallel-array$_FILESshape — normalize it yourself, or injectHttpRequestand usegetFiles()/getFile($name)instead.
use dev\winterframework\stereotype\web\PostMapping;use dev\winterframework\stereotype\web\RequestBody;use dev\winterframework\web\http\HttpUploadedFile;
class AvatarUpload{ public string $username; public HttpUploadedFile $avatar; // matches <input name="avatar" type="file"> // public array $avatar; // alternative: raw $_FILES entry}
#[PostMapping(path: '/avatar')]public function upload(#[RequestBody] AvatarUpload $req): array{ return ['tmp' => $req->avatar->getFilePath(), 'size' => $req->avatar->getSize()];}Typed list properties
Section titled “Typed list properties”DTO properties may be typed as StringList, IntegerList, or FloatList (namespace dev\winterframework\type) to receive JSON arrays (or repeated field[] form params) as typed collections. Numbers are coerced to strings for StringList; whole-number strings are coerced to integers for IntegerList (floats such as 1.5, 2.0, or "2.0" are rejected); integers and numeric strings are coerced to floats for FloatList. Anything else is rejected with 400 Bad Request.
use dev\winterframework\type\FloatList;use dev\winterframework\type\IntegerList;use dev\winterframework\type\StringList;
class BulkRequest{ public StringList $csvRow; // ["a", "b"] — or ["a", 1] → ["a", "1"] public IntegerList $ids; // [1, "2"] → [1, 2] public FloatList $scores; // [1.5, 2, "3.25"] → [1.5, 2.0, 3.25]}