Skip to content

HTTP Request Mapping with Winter Boot PHP 8 Attributes

Winter Boot uses PHP 8 native attributes for routing. Instead of maintaining a central routes file, you place mapping attributes directly on controller methods — or on the controller class itself to establish a base URI prefix. The framework reads these attributes at startup, builds a routing table, and dispatches every incoming request to the correct handler with all parameters already bound and type-cast.

#[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.

  • path string|array (required) The 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.

  • method array List of RequestMethod constants this handler accepts — for example, [RequestMethod::GET, RequestMethod::POST]. Defaults to all HTTP methods when omitted.

  • name string An optional human-readable name for this mapping. Useful for logging and debugging.

  • consumes array List of media types this handler consumes — for example, ['application/json']. Requests with a non-matching Content-Type are rejected.

  • produces array List of media types this handler produces — for example, ['application/json']. Used to negotiate the response Content-Type.

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”
ProductController.php
<?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 below
class 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 [];
}
}

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.

#[GetMapping]

Handles HTTP GET requests

#[PostMapping]

Handles HTTP POST requests

#[PutMapping]

Handles HTTP PUT requests

#[DeleteMapping]

Handles HTTP DELETE requests

#[PatchMapping]

Handles HTTP PATCH requests

use dev\winterframework\stereotype\web\GetMapping;
#[GetMapping(path: '/users')]
public function listUsers(): array
{
return $this->userService->findAll();
}

#[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.

  • name string Name of the incoming parameter. Defaults to the PHP variable name when omitted.

  • required bool Whether the parameter must be present in the request. Defaults to true. Set to false to make it optional.

  • defaultValue mixed Value used when the parameter is absent and required is false.

  • source string Where to read the value from. Defaults to 'request'. See the source table below.

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] (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] (namespace dev\winterframework\stereotype\web\RequestBody) binds the entire HTTP request body to a method parameter. Winter Boot deserialises JSON, XML, URL-encoded form bodies, and multipart forms into 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);
}
AddRequest.php
class AddRequest
{
public int $a;
public int $b;
}

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.

  • disableParsing bool Skips JSON/XML deserialisation. Defaults to false. When true, 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);
}

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 (namespace dev\winterframework\web\http\HttpUploadedFile) to get a typed object — getFilePath() for the temp path, plus getName(), getSize(), getError() / getErrorText(). Declare it as array instead to receive the raw $_FILES entry (name, type, tmp_name, error, size).
  • A multi-file field (<input type="file" name="photos" multiple>) keeps PHP’s parallel-array $_FILES shape — normalise it yourself, or inject HttpRequest and use getFiles() / 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()];
}

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]
}