PSR-17 w praktyce: HTTP Factories
PSR-17 standaryzuje tworzenie obiektów HTTP z PSR-7: requestów, response’ów, URI, strumieni i uploadowanych plików. Zamiast „lepić” obiekty ręcznie, korzystamy z fabryk — a reszta ekosystemu rozumie ten sam kontrakt.
Po co są fabryki z PSR-17?
- Tworzysz obiekty PSR-7 w jednolity sposób (request, response, stream, uri, uploaded file),
- łatwiej testujesz (podmieniasz fabryki w DI),
- bezbłędnie zachowujesz niezmienność obiektów PSR-7,
- masz kompatybilny most do PSR-18 (klienci HTTP).
Jak to wygląda u mnie
W kursie używam RequestFactory
, która składa obiekt requestu z menedżerów:
HeaderManager
, AttributesManager
, ParsedBodyManager
,
QueryParamsManager
, UploadedFilesManager
, a także z Stream
oraz
UploadedFileFactory
i budowniczego URI. To czysty PSR-17 w praktyce:
z zmiennych superglobalnych powstają obiekty zgodne z PSR-7.
<?php
declare(strict_types=1);
namespace DJWeb\Framework\Http\Request\Psr17;
use DJWeb\Framework\Http\HeaderManager;
use DJWeb\Framework\Http\Request\AttributesManager;
use DJWeb\Framework\Http\Request\ParsedBody;
use DJWeb\Framework\Http\Request\ParsedBodyManager;
use DJWeb\Framework\Http\Request\Psr7\Request;
use DJWeb\Framework\Http\Request\QueryParamsManager;
use DJWeb\Framework\Http\Request\UploadedFilesManager;
use DJWeb\Framework\Http\Stream;
use DJWeb\Framework\Http\UploadedFile\UploadedFileFactory;
use DJWeb\Framework\Http\Uri\UriBuilder;
use DJWeb\Framework\Http\UriManager;
use Psr\Http\Message\ServerRequestFactoryInterface;
use Psr\Http\Message\UploadedFileInterface;
use Psr\Http\Message\UriInterface;
class RequestFactory implements ServerRequestFactoryInterface
{
/**
* @param string $method
* @param $uri
* @param array<string, mixed> $serverParams
*
* @return Request
*/
public function createServerRequest(string $method, $uri, array $serverParams = []): Request
{
$uri = $uri instanceof UriInterface ? $uri : new UriManager($uri);
return new Request(
$method,
$uri,
new Stream('php://temp'),
new HeaderManager([]),
$serverParams,
[],
new QueryParamsManager([]),
new UploadedFilesManager([]),
new ParsedBodyManager(null),
new AttributesManager()
);
}
public function createFromGlobals(): Request
{
return new Request(
...$this->getRequestConstructorParams()
);
}
/**
* @return array<int, mixed>
*
* @throws \JsonException
*/
public function getRequestConstructorParams(): array
{
return [
$_SERVER['REQUEST_METHOD'] ?? 'GET',
new UriBuilder()->createUriFromGlobals(),
new Stream('php://input'),
new HeaderManager($this->getAllHeaders()),
$_SERVER,
$_COOKIE,
new QueryParamsManager($_GET),
new UploadedFilesManager($this->parseFiles($_FILES)),
new ParsedBodyManager(new ParsedBody()->get()),
new AttributesManager(),
];
}
/**
* @param array<string, mixed> $files
*
* @return array<string, UploadedFileInterface>
*/
private function parseFiles(array $files): array
{
$parsedFiles = [];
$files = array_filter($files, static fn (mixed $file) => is_array($file));
foreach ($files as $key => $file) {
$parsedFiles[$key] = UploadedFileFactory::createUploadedFile(
$file['name'],
$file['size'],
$file['error'],
$file['tmp_name'],
$file['type'] ?? null
);
}
return $parsedFiles;
}
/**
* @return array<string, string>
*/
private function getAllHeaders(): array
{
$headers = [];
foreach ($_SERVER as $name => $value) {
if (str_starts_with($name, 'HTTP_')) {
$headers[
str_replace(
' ',
'-',
ucwords(
strtolower(str_replace('_', ' ', substr($name, 5)))
)
)
] = $value;
}
}
return $headers;
}
}
Realny akcent PHP 8.5 w tej fabryce
W metodzie getAllHeaders mamy przykład zagnieżdżenia bardzo wielu funkcji. PHP 8.5 wprowadza nową składnię Pipe operator (|>
) w normalizacji nagłówków – zamiast kaskady zagnieżdżonych funkcji,
mam czytelny potok transformacji (strip prefix → zamień podkreślenia → ucwords
→ spacje na „-”. To ogromny wzrost czytelności przy mapowaniu $_SERVER
na header keys.
To jest dokładnie ten kierunek, w którym 8.5 robi różnicę: kod fabryki jest krótszy, czytelniejszy i łatwiej go utrzymać, bez łamania kontraktów PSR-7/17. Poniżej alternatywna wersja funkcji getAllHeaders wykorzystująca pełną moc nadchodzącego PHP 8.5
<?php
private function getAllHeaders(): array
{
return array_map(
callback: static fn(string $item) => substr($item, 5)
|> static fn(string $item) => str_replace('_', ' ', $item)
|> ucwords(...)
|> static fn(string $item) => str_replace(' ', '-', $item),
array: array_filter(
array: $_SERVER,
callback: static fn(mixed $value, string $key) => str_starts_with($key, 'HTTP_'),
)
);
}
Co dalej uprości 8.5 i okolice?
clone_with
– upraszcza tworzenie „zmodyfikowanych klonów”. W warstwie PSR-7 ułatwi implementacje metodwith*
po stronie Request/Response, a fabryki będą miały mniej „klejenia” wstępnego.- Nowe URL Parsing API (RFC 3986 w core) – fabryka URI przestaje utrzymywać własny parser; Obecnie w kursie opieram się na składni PHP 8.4 lecz już niebawem kurs dostanie aktualizację do PHP 8.5 gdzie zmian jak pokazana powyzej będzie dużo.
Co konkretnie dostarcza PSR-17 (w skrócie)?
- RequestFactory – createRequest(method, uri),
- ServerRequestFactory – createServerRequest(method, uri, serverParams),
- ResponseFactory – createResponse(code, reason),
- StreamFactory – createStream(...),
- UploadedFileFactory – createUploadedFile(...),
- UriFactory – createUri(string).
Wzorce projektowe, które tu grają
Przede wszystkim Factory Method + drobny Builder/Fluent po stronie PSR-7, a także Adapter (np. przy strumieniach/URI).
📖 Oficjalna specyfikacja PSR-17: https://www.php-fig.org/psr/psr-17/
👉 To tylko fragment pełnego kursu MasterPHP!
W kursie znajdziesz kompletną implementację PSR-17, wraz z wariantami pod PHP 8.5:
- Request/ServerRequest/Response/Stream/Uri/UploadedFile Factories,
- mapowanie superglobali do obiektów (bez „lepienia” w miejscu),
- użycie nowego URL API i
clone_with
do uproszczeń.