-
Notifications
You must be signed in to change notification settings - Fork 0
Migration Guide
Version 3.0 cleans up several long-standing design defects. Most upgrades are mechanical search-and-replace; the trickiest ones are the _parameters bag removal and the Request::createFromGlobals() move.
- Replace
InitPHP\HTTP\Message\Interfaces\*withPsr\Http\Message\*. - Replace
Request::createFromGlobals()withServerRequest::createFromGlobals(). - Replace any
$request->name = $value/$request->all()/$request->get('name')with the PSR-7 attribute or parsed-body API. - Replace
$request->sendRequest()with explicit(new Client())->sendRequest($request). - Pre-encode array bodies before passing them to
Client::sendRequest()(the helpersend_request()still auto-encodes). - If you depended on the legacy "no timeout" behaviour, call
->withTimeout(0)explicitly. - If you imported the misspelled
Facadeble/FacadebleInterface, switch toFacadable/FacadableInterface(old names still resolve but are@deprecated).
The InitPHP\HTTP\Message\Interfaces\* mirror set (MessageInterface, RequestInterface, ResponseInterface, ServerRequestInterface, StreamInterface, UriInterface) is gone. Each had mandatory setX / outX mutators that conflicted with PSR-7's immutability prose; any third-party PSR-7 type fed into a function type-hinted against them was rejected at compile time.
- use InitPHP\HTTP\Message\Interfaces\RequestInterface;
+ use Psr\Http\Message\RequestInterface;The concrete classes still implement everything PSR-7 requires; only the package-local interfaces are gone.
Moved to ServerRequest (its conceptually correct home), and now stateless:
- $request = InitPHP\HTTP\Message\Request::createFromGlobals();
+ $request = InitPHP\HTTP\Message\ServerRequest::createFromGlobals();The new factory takes optional $server, $get, $post, $cookies, $files arguments — pass them explicitly under long-running PHP runtimes (Swoole, RoadRunner, Octane, FrankenPHP) and in tests so you don't depend on superglobal state.
__get / __set / __isset / all() / get() / has() / merge() no longer exist on Request. Use the PSR-7 alternatives:
- $request->name;
- $request->get('name');
- $request->has('name');
- $request->all();
- $request->merge($_GET, $_POST, $rawData);
+ $parsed = $request->getParsedBody() ?? [];
+ $name = $parsed['name'] ?? null;
+
+ // For middleware-set state:
+ $request = $request->withAttribute('name', $value);
+ $name = $request->getAttribute('name');ServerRequest::createFromGlobals() populates parsedBody automatically when the request advertises a known Content-Type — JSON, urlencoded, multipart — so the typical case "read the field the client submitted" works out of the box.
The convenience hook that built and dispatched a client inline is gone — it hard-coded a fresh Client per call and made DI / testing painful:
- $response = $request->sendRequest();
+ $response = (new InitPHP\HTTP\Client\Client())->sendRequest($request);Or via the facade:
+ $response = InitPHP\HTTP\Facade\Client::sendRequest($request);The previous client sniffed $request instanceof Request and silently turned the _parameters bag into a JSON body. That violated PSR-18's "send what you got" contract. v3 only accepts string | resource | StreamInterface | null bodies.
- // Old: $request had ->name = 'Ada' and the client encoded it for us.
- (new Client())->sendRequest($request);
+ $request = $request
+ ->withBody(new Stream(json_encode($payload), null))
+ ->withHeader('Content-Type', 'application/json');
+ (new Client())->sendRequest($request);The send_request() global helper still handles convenience JSON-encoding for arrays — see Helpers.
Client::fetch() / get() / post() / put() / patch() / delete() / head() previously coerced DOMDocument, SimpleXMLElement, array, and "any object with __toString() or toArray()" into a body. That responsibility belongs in the application — different APIs need different serialisation choices. v3 accepts only string | resource | StreamInterface | null.
If you need the old behaviour for arrays specifically, the send_request() helper still does it; for DOM / SimpleXML, encode before passing in:
- $client->post($url, $dom);
+ $client->post($url, $dom->saveHTML());
- $client->post($url, $simpleXml);
+ $client->post($url, $simpleXml->asXML());Defaults: 30 s request timeout (CURLOPT_TIMEOUT), 10 s connect timeout (CURLOPT_CONNECTTIMEOUT). The old default was 0 (no timeout). If you specifically rely on infinite waits (long-poll, SSE):
$client = (new Client())->withTimeout(0)->withConnectTimeout(10);UploadedFile::__construct(?int $size, ...) and UploadedFile::getSize(): ?int now match the PSR-7 contract. Code that did int $size = $file->getSize() will need a null check on streams whose size isn't reportable.
- use InitPHP\HTTP\Facade\Traits\Facadeble;
- use InitPHP\HTTP\Facade\Interfaces\FacadebleInterface;
+ use InitPHP\HTTP\Facade\Traits\Facadable;
+ use InitPHP\HTTP\Facade\Interfaces\FacadableInterface;The old names remain as @deprecated aliases and continue to work, but will be removed in 4.0.
Previously, when $second > 0, only the Refresh header was set — crawlers and HTTP libraries that ignore the non-standard Refresh could not follow the redirect. v3 always sets Location and adds Refresh on top when a delay is requested.
No code changes needed if you used redirect(..., $status, 0); if you set a non-zero delay and relied on the absence of Location, that absence is gone.
Uses JSON_THROW_ON_ERROR and translates failure into InvalidArgumentException. Code that used to silently produce false bodies on unencodable input will now throw.
The Content-Type also gained an explicit charset:
- Content-Type: application/json
+ Content-Type: application/json; charset=utf-8The 500 reason phrase was previously 'Internal ServerRequest Error'. v3 emits the canonical 'Internal Server Error'. Log-aggregation rules that match on the literal old string will miss new responses.
PSR-7's hard requirement; the old implementation propagated RuntimeException from detached streams. v3 returns an empty string instead. If your code relied on the throw to detect a detached stream, switch to $stream->getContents() (still throws) or check via isset/eof first.
The target = null (pure in-memory string) backend used to prepend at position 0 and never advance the cursor. v3 overwrites from the current position and advances tell() correctly. If you wrote code that relied on the broken prepend behaviour, swap to new Stream($newPrefix . $oldBody, null).
The previous getSize() < 1 test mis-classified pipes/sockets as empty. v3 returns false for both predicates when the size is null (indeterminate). Callers branching on "unknown" must check getSize() === null themselves.
v2 always raised EmitBodyException for both "headers already sent" and "output buffer dirty" failure modes. v3 splits them — EmitHeaderException for the headers case, EmitBodyException for the body case. Both still extend \RuntimeException, so single-catch handlers keep working.
Private method renamed to Stream::stringToResource (private). Beyond the spelling fix, the materialised detach handle now preserves the cursor position instead of always returning a handle positioned at byte zero. Only affects code that explicitly called the private method via reflection (don't do that).
Renamed to updateHostFromUri. The trait is internal to this package; only matters if you extended one of the trait consumers and overrode the method.
Response::__construct() now accepts '2', '3', '3.0' in addition to '1.0', '1.1', '2.0'. If you constructed with '2' previously and got an InvalidArgumentException, that's now valid.
Seven methods gained explicit language-level return types to satisfy the tightened psr/http-message: ^2.0 contract (v1 left them untyped at the interface level; v2 declares them):
| Method | Added return type |
|---|---|
Stream::close() |
: void |
Stream::seek($offset, $whence = SEEK_SET) |
: void |
Stream::rewind() |
: void |
MessageTrait::getHeader($name) |
: array |
UploadedFile::getStream() |
: StreamInterface |
UploadedFile::moveTo($targetPath) |
: void |
Uri::__toString() |
: string |
Existing PHPDoc @return lines already documented these types — only the runtime signature changed. No call-site code needs to change.
Why it matters mostly under PHP 7.4 + PSR-7 v2. PHP 7.4 enforces return-type covariance strictly: an untyped implementation cannot satisfy a typed interface return (e.g.
: string), and the result is a fatal at class-load time: "Declaration of InitPHP\HTTP\Message\Uri::__toString() must be compatible with Psr\Http\Message\UriInterface::__toString(): string". PHP 8.0+ is more lenient and accepted the older signatures silently. The fix is uniform across all supported PHP versions.
However, if you extended any of these classes and overrode one of those methods, your override's signature must now match the new return type (or stay untyped — PHP allows that). If your override declared a different return type (e.g. : bool), it will now fail with a fatal at class load time. Update overrides accordingly.
- PSR-7 / PSR-17 / PSR-18 spec behaviour — the integration test suites still pass 100%.
-
All
with*()mutators on every message type — names, signatures, and immutability behaviour are unchanged. -
The static facades —
InitPHP\HTTP\Facade\{Client, Emitter, Factory}still resolve to the same singleton with the same surface. -
send_request()global helper — same signature, accepts arrays / objects withtoArray()/__toString()exactly as before.
If something on this list broke for you, please file an issue at github.com/InitPHP/HTTP/issues.
initphp/http · MIT License · part of the InitPHP family
Source · Issues · Discussions · Packagist · Contributing · Security Policy
Getting Started
PSR-7 Messages
PSR-17 Factories
PSR-18 Client
Emitter (SAPI)
Static Facades
Recipes
Reference
Migration & Help