Search by

ez-php / http

AU9500

HTTP message objects for the ez-php framework — immutable Request, Response, RequestFactory, and ResponseEmitter. Zero dependencies.

Package info

github.com/ez-php/http

pkg:composer/ez-php/http

Statistics

Installs: 15 718

Dependents: 29

Suggesters: 1

Stars: 0

Open Issues: 0

2.7.1 2026-10-30 15:41 UTC

README

HTTP message objects for PHP — immutable Request, Response, RequestFactory, ResponseFactory, StreamedResponse, ResponseEmitter, Cookie, and UploadedFile. Zero dependencies.

CI

Requirements

  • PHP 8.5+

Installation

composer require ez-php/http

Usage

Request

use EzPhp\Http\Request;
use EzPhp\Http\RequestFactory;

// Create from PHP globals (web context)
$request = RequestFactory::createFromGlobals();

echo $request->method();           // GET, POST, ...
echo $request->uri();              // /users/42
echo $request->query('page');      // query string value
echo $request->input('name');      // POST body value

// Strict typed body values — null unless the value is exactly that type
$qty   = $request->integer('qty');              // 3 or "3" → 3; "3abc", 3.0, true → null
$gift  = $request->boolean('gift');             // true/false, "true"/"false"/"1"/"0", 1/0; else null
$price = $request->decimal('price', scale: 2);  // "19.9" → "19.90"; "19.999" → null (never rounded)
$slot  = $request->enum('slot', Slot::class);   // backed enum case by value; unknown value or wrong type → null
$id    = $request->paramInteger('id');          // route parameter, same strict rules as integer()
$page  = $request->queryInteger('page');        // query-string value, same strict rules as integer()
echo $request->header('accept');   // request header (case-insensitive)
echo $request->cookie('session');  // cookie value
echo $request->param('id');        // route parameter (set by router)
echo $request->rawBody();          // raw request body string
echo $request->ip(['10.0.0.1']);   // client IP; X-Forwarded-For honoured only from the listed trusted proxies

// Uploaded files
$file = $request->file('avatar');  // returns UploadedFile|null

// Immutable — returns a new instance
$request = $request->withMethod('PATCH');
$request = $request->withParams(['id' => '42']);
$request = $request->withAttribute('hero', $hero);   // set by middleware…
$hero = $request->attribute('hero');                // …read by the action

Response

use EzPhp\Http\Response;
use EzPhp\Http\ResponseEmitter;
use EzPhp\Http\ResponseFactory;

$response = new Response(body: 'Hello', status: 200);

// Fluent header chaining — each call returns a new instance
$response = $response
    ->withHeader('Content-Type', 'application/json')
    ->withHeader('X-Request-Id', 'abc123');

// Factory helpers
$json     = ResponseFactory::json(['id' => 1]);
$redirect = ResponseFactory::redirect('/dashboard');
$html     = ResponseFactory::html('<h1>Hello</h1>');

// Send to the browser
(new ResponseEmitter())->emit($response);

emit() accepts any ResponseInterface, including StreamedResponse. Inside an ez-php application use $app->send($request, $response) instead: it reports stream failures and runs terminable middleware after the body has been sent.

Streamed responses

use EzPhp\Http\Sse\SseEvent;
use EzPhp\Http\StreamedResponse;

// File download — the resource is read in chunks and closed afterwards
$download = StreamedResponse::download($storage->getStream('reports/q3.csv'), 'q3.csv', $size, 'text/csv');

// Server-Sent Events
$events = StreamedResponse::sse(function (): \Generator {
    yield new SseEvent(json_encode(['progress' => 50], JSON_THROW_ON_ERROR), 'progress');
});

Headers are sent before the first chunk, so an exception inside the generator can no longer become an error page. Check authorisation and inputs before returning a StreamedResponse.

Cookie

use EzPhp\Http\Cookie;

$cookie = new Cookie(
    name:     'session',
    value:    'abc123',
    ttl:      3600,      // Max-Age; 0 = session cookie, negative = delete (Max-Age=0)
    path:     '/',
    secure:   true,
    httpOnly: true,
    sameSite: 'Lax',     // Lax, Strict or None (None requires secure); '' omits it
);

$cookie->toHeaderValue(); // session=abc123; Max-Age=3600; Path=/; Secure; HttpOnly; SameSite=Lax

// Usually you queue it on the response instead; ResponseEmitter sends it:
$response = $response->withCookie('session', 'abc123', 3600, secure: true, httpOnly: true, sameSite: 'Lax');

To delete a cookie, send it again with a negative TTL (and the same path/domain it was set with): the header carries Max-Age=0 and the browser drops it.

$response = $response->withCookie('remember', '', -1, secure: true, httpOnly: true);

Middleware that rewrites queued cookies (e.g. to encrypt their values) replaces the whole list with withCookies(list<Cookie>); on the request side, Request::withCookies(array) returns a copy with a new cookie map:

$response = $response->withCookies(array_map(
    fn (Cookie $c): Cookie => new Cookie($c->name(), strrev($c->value()), $c->ttl(), $c->path()),
    $response->cookies(),
));
$request = $request->withCookies(['session' => 'decoded-value']);

UploadedFile

use EzPhp\Http\UploadedFile;

$file = $request->file('avatar'); // returns UploadedFile|null

if ($file !== null && $file->isValid()) {
    $file->moveTo('/var/www/uploads/' . basename($file->originalName()));
    echo $file->mimeType();       // 'image/jpeg' (client-reported, not verified)
    echo $file->size();           // bytes
}

Classes

Class Description
RequestInterface Contract for the Request value object
Request Immutable HTTP request value object (final readonly class)
RequestFactory Builds a Request from PHP superglobals
Response HTTP response value object; clone-based withHeader() (case-insensitive replace)
Headers Headers::get($response->headers(), 'content-type') — case-insensitive lookup; set()/normalize() used by both responses
ResponseFactory Static helpers: json(), redirect(), html(), text(), noContent()
ResponseEmitter Sends any ResponseInterface — headers via HeaderSenderInterface, body via OutputInterface, chunk by chunk for streams
HeaderSenderInterface Abstraction over header() calls (injectable for testing)
NativeHeaderSender Default HeaderSenderInterface implementation using PHP's header()
Cookie Immutable value object for Set-Cookie attributes; toHeaderValue() produces the header string
UploadedFile Wraps a $_FILES entry; isValid(), moveTo(), originalName(), mimeType(), size(), error(); fromLocalFile() for files PHP's SAPI did not receive

License

MIT — Andreas Uretschnig