129 KiB
Guzzle Upgrade Guide
7.0 to 8.0
Guzzle 8 is a major release that raises the minimum PHP version, updates the Guzzle dependency stack, adopts stricter PSR-7 header and request method behavior, changes some network exception classification, and tightens validation for request options, protocols, transport settings, cookies, and native method signatures. It also adds generic PHPDoc types to async APIs for static analysis.
PHP Version and Dependencies
Guzzle 8 requires PHP ^7.4 || ^8.0. Guzzle 7 supported PHP
^7.2.5 || ^8.0.
Guzzle 8 also requires Guzzle Promises 3.x and Guzzle PSR-7 3.x. If your application pins either package directly, update those constraints to allow the new major versions.
Read the Guzzle PSR-7 3.x upgrade guide as part of every Guzzle 8 upgrade. Guzzle's normal request and response APIs use Guzzle PSR-7, so PSR-7 changes can affect applications even when they do not instantiate PSR-7 classes directly. This guide calls out the most common inherited PSR-7 changes, but it does not repeat every PSR-7 behavior change.
Pay particular attention to that PSR-7 upgrade guide if your application constructs requests, modifies responses, sets headers, uploads files, builds multipart requests, works with URIs, or inspects streams.
Also read the Guzzle Promises 3.x upgrade guide if you use async requests, custom handlers, middleware, pools, or promise helpers.
Guzzle 8 now requires psr/http-factory:^1.0 directly.
IPv6 Origin Canonicalization
Guzzle PSR-7 3.x serializes IPv6 hosts in native URIs in their RFC 5952
canonical form, and its UriComparator::isCrossOrigin() canonicalizes
bracketed IPv6 literals from any PSR-7 implementation before comparing
origins. Redirects between equivalent spellings of one IPv6 address are
therefore same-origin in Guzzle 8: Authorization and Cookie headers, the
auth option, cURL authentication options, and the same-scheme Referer path
and query may be retained where Guzzle 7 stripped or reduced them, and absolute
Digest domain protection spaces can cover an equivalent spelling of the same
address.
Guzzle 8 also keys its own host-scoped state, the Digest challenge cache and
the cookie jar, on the canonical IPv6 form, so equivalent spellings share one
entry instead of fragmenting state. A cached Digest challenge may be reused
preemptively across spellings instead of causing another 401 probe. Host-only
cookies extracted from one spelling match requests using another, persisted
cookie domain text is canonical, and equivalent cookie entries coalesce. Bare
IPv6 cookie domains, which the cookie API permissively accepts, canonicalize
without gaining brackets, and bare and bracketed forms remain distinct
identities. Cookie-domain matching remains exact-only for IP literals and IP
addresses, and DNS suffix matching now requires both the cookie domain and the
request host to be valid non-literal, nonnumeric host names. A host counts as
numeric on its rightmost label alone, so a cookie domain such as
svc.0xdeadbeef is exact-only in Guzzle 8 although Guzzle 7 reads it as a name
and matches its subdomains.
Configured proxy endpoints are not canonicalized, no-proxy matching was
already representation-independent, and the URI text sent to the transport is
not rewritten: a foreign UriInterface that reports a noncanonical IPv6
spelling is serialized as supplied. IPvFuture literals, zone-bearing values,
and invalid bracketed text keep their ASCII-case-folded textual identity in
these Digest and cookie comparisons.
PSR-7 Header Values
Guzzle 8 uses Guzzle PSR-7 3.x, and several of its behavior changes surface through normal Guzzle client usage. This section summarizes the inherited PSR-7 changes most likely to affect Guzzle users; read the Guzzle PSR-7 3.x upgrade guide for the complete list.
Header values passed through the headers request option or PSR-7 request APIs
must now be strings or non-empty arrays of strings. Empty strings remain valid
explicit header values, but empty arrays, null, false, integers, floats, and
other non-string values are no longer cast or accepted.
If your application builds headers from configuration, user input, or typed domain values, normalize them before creating or sending requests:
// 7.x, no longer accepted in 8.0
$client->request('GET', '/', [
'headers' => ['Api-Version' => 1],
]);
// 8.0
$client->request('GET', '/', [
'headers' => ['Api-Version' => '1'],
]);
Request Method Casing
Guzzle 8 preserves explicitly provided request method casing. HTTP method names
are case-sensitive, so Guzzle now sends the method exactly as provided and
applies built-in method-specific behavior only to exact standard method names
such as GET, HEAD, POST, and PUT.
If you previously relied on new Request('get', ...) or
$client->request('get', ...) being sent or treated as GET, normalize the
method before constructing or sending the request:
$client->request('GET', 'https://example.com');
The convenience methods such as $client->get(), $client->post(), and their
async variants continue to use uppercase standard methods.
Auth Request Option Changes
Digest authentication is no longer implemented with cURL CURLOPT_HTTPAUTH and
CURLOPT_USERPWD. Guzzle supports legacy non-session Digest challenges without
qop and challenges with qop=auth; session algorithms require qop.
auth-int is not supported. To use libcurl's native Digest implementation
instead, omit auth and configure cURL options directly with a cURL handler,
including CURLOPT_HTTPAUTH => CURLAUTH_DIGEST and CURLOPT_USERPWD.
Guzzle 8 no longer treats ntlm as a built-in auth type. If an existing
NTLM-only integration must be kept temporarily, configure cURL options directly
with a cURL handler and a libcurl build that still supports NTLM:
$client->request('GET', '/', [
'curl' => [
CURLOPT_HTTPAUTH => CURLAUTH_NTLM,
CURLOPT_USERPWD => 'username:password',
],
]);
This is not a long-term migration path. curl/libcurl has deprecated NTLM because it is weak, deprecated by Microsoft, and does not work over HTTP/2 or HTTP/3. curl made NTLM opt-in in curl 8.20.0 and plans to remove support in September 2026.
Digest requests that carry a body send the initial unauthenticated probe with an
empty body and Content-Length: 0, as libcurl does. The body is sent on
authenticated attempts: once in the normal one-challenge handshake, and again if
a stale-nonce challenge must be retried. Because of this, non-seekable request
bodies work with Digest authentication unless a stale-nonce retry forces a
rewind. Followed redirects also rewind the body under the normal redirect rules,
independently of Digest. If the server answers the probe with anything other
than a 401 response, a 407 proxy challenge, or a followable redirect, the request
fails with a GuzzleHttp\Exception\ResponseException carrying that response,
because the probe did not represent the original request; remove the auth
option for endpoints that do not require authentication. See "Differences from
libcurl's Digest implementation" below.
Differences from libcurl's Digest implementation
Guzzle 8's Digest middleware deliberately diverges from libcurl, which
implemented the auth option's Digest support in Guzzle 7, in the following
ways:
- Unchallenged probes fail loudly. Body-bearing requests are probed with an
empty body. If the server answers the probe with anything other than a 401
response, a 407 proxy challenge, or a followable redirect, Guzzle throws
GuzzleHttp\Exception\ResponseExceptionwith that response attached. This exception pre-empts theClientExceptionorServerExceptionthathttp_errorswould otherwise raise for such a response;catch (RequestException $e)catches both. For unchallenged sub-300 probe responses libcurl instead re-issues the request unauthenticated, which can cause the server to process both the empty probe and the replay; for other statuses it surfaces the probe response. If a server does not require authentication, remove theauthoption. - Probe payload headers. The probe strips the payload-describing headers:
Expect,Transfer-Encoding,Trailer,Content-Range,Content-Encoding,Content-MD5,Digest,Content-Digest, andRepr-Digest, and forcesContent-Length: 0.Content-Typeis kept, as libcurl does. libcurl's probe is bodyless but not header-identical: it keeps a caller-suppliedExpect, keeps HTTP/1.1Transfer-Encoding, sending a chunked probe with a zero chunk and noContent-Length, and forwards other caller headers untouched. Guzzle's stripping is deliberately stricter. auth-intis rejected. Challenges offering onlyqop=auth-intare ignored. libcurl selectsauth-intwhenauthis absent but hashes an empty entity body, providing no actual body integrity.- Challenge selection. With multiple Digest challenges, Guzzle picks the
strongest supported algorithm. libcurl decodes the first Digest
WWW-Authenticateheader field and ignores later Digest fields. Multiple Digest challenges inside one field are parser-dependent in curl. - Strict challenge parsing. Challenges with duplicated or malformed
parameters are rejected. When another usable Digest challenge is present, it
is used instead, always across separate
WWW-Authenticateheader fields and in some same-field shapes. The 401 is returned untouched only when no usable Digest challenge remains. libcurl is lenient: later duplicate values overwrite earlier ones,staleanduserhashflags are sticky once set, and malformed tails are tolerated after enough valid material has been parsed. - Challenge reuse. Cached challenges authorize later body-less requests
preemptively with incremented nonce counts; body-bearing requests always
re-probe, and only challenges that produced an authenticated success are
cached. libcurl kept Digest state per easy handle, which Guzzle 7 reset between
requests, so 7.x re-handshook every request. Guzzle honors the RFC 7616
domainparameter when scoping reuse, and curl's Digest state has nodomainhandling. Guzzle generates a fresh cnonce per Authorization header, where curl's non-SSPI implementation reuses one cnonce per nonce while incrementingnc. Whendomainis absent the cached challenge covers the whole origin, so on origins that host multiple realms or trust boundaries a preemptive request can disclose the username or userhash and password-derived response material for one realm to another same-origin endpoint before it challenges; use separate origins, a server-sidedomain, or disable reuse by replacing the default auth middleware withGuzzleHttp\Middleware::auth(false)as shown in theauthrequest option documentation. - Per-leg observability. Each handshake leg is a separate transfer:
on_stats,on_headers, andprogressfire per leg, anddelayapplies once, before the first Digest leg: the probe, or a preemptive request when challenge reuse applies. libcurl performed the whole handshake inside one transfer with one stats callback. - Streaming sinks. With
stream => trueplus a configuredsink, a Digest request drains the final body into the sink, protecting sinks from challenge bodies on handlers that ignorestream. A non-digest request on the stream handler leaves the sink untouched. - Proxy Digest (407) is not implemented. The response passes through. The
built-in cURL handlers allow proxy credentials but not
CURLOPT_PROXYAUTH, and libcurl defaults proxy auth to Basic, so proxy Digest requires a custom handler.
Non-seekable request bodies work for the normal handshake because the probe never consumes them. A stale-nonce retry after the body has been consumed still requires a seekable body, the same limit libcurl has.
Built-in Basic and Digest authentication continue to work for clients using
Guzzle's default handler or GuzzleHttp\HandlerStack::create($handler), but
they are now applied by the default auth middleware instead of while the client
constructs the request. If you pass a raw custom handler directly to Client,
remove default middleware, or build a handler stack manually, Basic and Digest
authentication will only be applied when your stack includes
GuzzleHttp\Middleware::auth(). Unknown auth type strings are left in the
request options for custom middleware or custom handlers.
Guzzle also validates non-empty auth arrays before applying them: indexes 0
and 1 must be username and password strings, and index 2, when present, must
be a string or null. Invalid auth arrays that previously emitted warnings,
coerced values, or did nothing now throw
GuzzleHttp\Exception\InvalidArgumentException.
// Valid:
$client->request('GET', '/', [
'auth' => ['username', 'password', 'basic'],
]);
// Invalid in 8.0:
$client->request('GET', '/', [
'auth' => ['username'],
]);
Guzzle 8's built-in authentication middleware rejects Basic auth credentials
when the username contains a colon or either the username or password contains
an ASCII control character. Guzzle 7 encoded and sent these values. Colons
remain valid in passwords.
Guzzle 8 also no longer forwards the generic auth request option when
automatic redirects cross origin. Guzzle already removed the Authorization and
Cookie headers and cURL HTTP authentication options on cross-origin redirects;
this now also applies to handler-visible auth state.
Same-origin redirects continue to preserve auth. If an application or custom
handler intentionally reused auth across redirected origins, disable automatic
redirects or handle redirects manually so each origin receives explicit
credentials.
Exception Hierarchy and Classification
Some Guzzle 8.0 exception changes add intermediate classes, while others
intentionally reclassify specific failures. Broad TransferException and
GuzzleException catch blocks still catch all Guzzle transfer failures, and
RequestException still catches response-aware and other non-network request
failures. More specific catch blocks need auditing: Guzzle 8.0 splits timeout
failures into connect-phase, no-response network, and response-aware timeout
classes, and the built-in cURL and stream handlers classify more transport
failures by transfer phase. This section covers upgrading from Guzzle 7.x to
Guzzle 8.0. The ConnectException inheritance change happened earlier, in
Guzzle 7.0.0, when it moved out from under RequestException; see the 6.0 to
7.0 notes for that migration. The hierarchy changes are easiest to compare in
the two trees below.
Guzzle 7.x uses this hierarchy:
. \RuntimeException
└── TransferException (implements GuzzleException)
├── ConnectException (implements NetworkExceptionInterface)
└── RequestException (implements RequestExceptionInterface)
├── BadResponseException
│ ├── ClientException
│ └── ServerException
└── TooManyRedirectsException
Guzzle 8.0 uses this hierarchy:
. \RuntimeException
└── TransferException (implements GuzzleException)
├── HandlerClosedException
├── NetworkException (implements NetworkExceptionInterface)
│ ├── ConnectException
│ │ └── ConnectTimeoutException
│ └── NetworkTimeoutException
└── RequestException (implements RequestExceptionInterface)
└── ResponseException
├── BadResponseException
│ ├── ClientException
│ └── ServerException
├── ResponseTransferException
│ └── ResponseTimeoutException
└── TooManyRedirectsException
NetworkException is new in Guzzle 8.0 as the base class for no-response
network failures. Throughout Guzzle 7.x, ConnectException implements
Psr\Http\Client\NetworkExceptionInterface directly and there is no
Guzzle-specific network base class. Reusable packages that support both Guzzle
7.x and 8.0 should catch Psr\Http\Client\NetworkExceptionInterface for
no-response network failures. Applications or packages that require Guzzle 8.0
may catch GuzzleHttp\Exception\NetworkException for all no-response network
failures. Keep catching ConnectException only when handling connection
establishment failures specifically.
Guzzle 8.0 also makes response-aware request failures explicit. Guzzle 7.x does
not have ResponseException, and response-aware request failures remain under
RequestException throughout Guzzle 7.x. In Guzzle 8.0, ResponseException is
the base class for request failures where response headers were received and a
response object is available. ResponseTransferException is used for
transfer-level failures after headers, including response-aware network,
protocol, content-decoding, partial-body, and response-body transfer failures.
BadResponseException and TooManyRedirectsException also extend
ResponseException; custom subclasses of those existing classes, or of
ClientException and ServerException, must not override the now-final
ResponseException::__construct().
Only this branch exposes response access: RequestException no longer stores
responses, no longer accepts a response constructor argument, and no longer has
getResponse() or hasResponse() methods. Catch ResponseException, or test
with instanceof ResponseException, before calling getResponse().
Timeout exception classes are now split by the transport phase the handler can
determine. ConnectTimeoutException is thrown for detected connect timeouts
(DNS resolution, TCP connect, proxy CONNECT, or TLS handshake). It extends
ConnectException, so code that catches ConnectException will also catch
connect timeouts. NetworkTimeoutException is thrown for other detected
transport timeouts before response headers are received. It extends
NetworkException, but not ConnectException. ResponseTimeoutException is
thrown for response transfer timeouts after response headers are received. It
extends ResponseTransferException and exposes the response.
Timeouts that originate from caller-supplied PSR-7 streams are not transport
timeouts. Reading the request body stream, including size detection,
stringification, rewind, and upload reads, is a RequestException before a
response and a ResponseException after response headers. A slow response
sink write is a ResponseException once a response exists, or a
RequestException otherwise.
If a handler throws Error, TypeError, or another non-Exception Throwable
before returning a promise, Client::sendAsync() returns a rejected promise.
Waiting on it rethrows the original throwable. During request and response body
handling outside native cURL callbacks, Guzzle still wraps \Exception failures
as RequestException or ResponseException where appropriate, while
non-Exception throwables propagate unchanged. Native cURL callbacks remain
different. A throwable from a cURL sink write is wrapped as
ResponseException when a response was received, or RequestException
otherwise, and the cURL handlers continue to report CURLE_WRITE_ERROR as
on_stats handler error data.
HandlerClosedException is new in Guzzle 8.0. It extends TransferException
and is used when an explicitly closed CurlMultiHandler rejects transfers that
are still pending, including delayed transfers. Existing Guzzle 7.x code should
not normally see this exception just by upgrading. Guzzle 7.x did not have this
exception or a public cURL handler close() method, and destructor cleanup did
not reject pending promises. If you add deterministic cleanup with
CurlMultiHandler::close(), wait for or cancel outstanding transfers first, or
handle HandlerClosedException or TransferException for pending promises you
may still observe.
When updating catch blocks for Guzzle 8.0, catch the more specific no-response
and response-aware failures before RequestException:
use GuzzleHttp\Exception\NetworkException;
use GuzzleHttp\Exception\RequestException;
use GuzzleHttp\Exception\ResponseException;
use GuzzleHttp\Exception\ResponseTransferException;
try {
$client->request('GET', $uri);
} catch (NetworkException $e) {
// No-response network failures, including reclassified cURL and stream
// handler failures.
} catch (ResponseTransferException $e) {
$response = $e->getResponse();
// Transfer-level failures after response headers were received.
} catch (ResponseException $e) {
$response = $e->getResponse();
// Other request failures after response headers were received.
} catch (RequestException $e) {
// Request failures where Guzzle does not expose a response object.
}
The built-in handlers also reclassify several failures that Guzzle 7.x reported
as RequestException or ConnectException:
- Connection-establishment failures (DNS and proxy resolution, TCP connect, TLS
setup, and QUIC connect) are
ConnectException. - Other failures with no response, such as send and receive errors and
no-response HTTP/2 and HTTP/3 protocol errors, are
NetworkException. - Response-transfer failures after response headers were received are
ResponseTransferException. This includes response-aware cURL connection, network, protocol, content-decoding, partial-body, and response body transfer failures. Response-body network stalls areResponseTimeoutException, whilesinkwrite failures, progress callback failures, deterministic response size/platform-limit failures, or request-body stalls after headers are plainResponseExceptioninstances. - Post-transfer response finalization failures are also plain
ResponseException. A seekable response sink that fails to rewind does not become aResponseTransferException. Non-seekable sinks are not rewound, and source close cleanup after a complete stream-handler download is best effort. - Other non-transfer or local failures that occur after a response was received
are
ResponseException.
This applies to both the cURL and stream handlers; the exact error codes and messages each one maps onto these classes are an implementation detail.
The cURL handler no longer treats non-101 informational responses such as
100 Continue, 102 Processing, or 103 Early Hints as the final response. If
a cURL transfer fails after receiving only one of those interim responses,
Guzzle now reports a no-response failure such as NetworkException instead of a
ResponseTransferException carrying the interim 1xx response. Code that
previously caught this path with RequestException or
RequestExceptionInterface should catch NetworkExceptionInterface or
TransferException instead. 101 Switching Protocols is unchanged and is still
surfaced as a response.
The built-in cURL and stream handlers now reject a response subject to body
framing when its Content-Length is malformed, conflicting, or present with
Transfer-Encoding. Equivalent decimal duplicates remain valid, including
leading zeros. Framing failures detected from a complete header block occur
before on_headers sees that response or any of its body bytes are delivered,
and raise ResponseTransferException. A transport that rejects the block first
can instead report its existing transfer failure. When a handler needs the
length as a PHP integer, a valid value above PHP_INT_MAX raises plain
ResponseException with the underlying OverflowException available through
getPrevious(). The stream handler does not impose this platform limit on
stream => true responses. Framing validation does not apply to responses to
HEAD, any 1xx, 204, 304, or successful CONNECT. 205 Reset Content
remains subject to framing validation.
The stream handler also raises ResponseTransferException after draining a
non-streamed response when a valid, positive Content-Length declares more
bytes than it receives. For decoded gzip and deflate, the length applies to
encoded bytes before decompression, and the original values remain in
x-encoded-content-length. Valid stream => true responses remain lazy after
header validation.
For chunked responses through the stream handler, the transport resource's
wrapper_data can now retain the original Transfer-Encoding. This is visible
on streamed bodies and to custom stream factories. On PHP 8.1.20+, 8.2.7+, and
8.3+, progress also counts raw chunk framing coalesced with the response
headers. Later socket reads were already counted before decoding. Guzzle's
response continues to expose the decoded body without that consumed header.
The deprecated RequestException::wrapException() method was removed. Create a
RequestException directly for request failures where Guzzle does not expose a
response object. Its third constructor argument is now the exception code,
followed by the previous exception. For failures with a response, create
ResponseException, ResponseTransferException, ResponseTimeoutException,
BadResponseException, ClientException, ServerException, or
TooManyRedirectsException instead. If you used the removed
getHandlerContext() methods to classify failures, use the more granular
exception classes above instead, or use on_stats when you need handler timing
or statistics. GuzzleHttp\Exception\InvalidArgumentException remains outside
the transfer exception hierarchy and is still used for invalid configuration or
request option values that can be rejected before a transfer starts.
RequestException::create() no longer accepts a handler-context array. Its
fourth argument is now the optional BodySummarizerInterface; remove any
handler-context argument and use the new exception classes or on_stats when you
need transport details.
TransferException itself now also requires the failing request as its second
constructor argument, and its message argument is no longer optional;
getRequest() is available on the entire transfer exception hierarchy. The
subclasses' own constructors, such as RequestException and
ConnectException, already required a request in Guzzle 7, so only code
constructing the base class changes: replace new TransferException('msg')
with new TransferException('msg', $request).
Request Body Framing
Before sending a body, the built-in cURL and stream handlers now finalize the
request framing. Each Content-Length value must be a non-negative decimal
integer that the selected transport can represent. Multiple values are allowed
only when they agree. Guzzle emits a single canonical value, and it must match
the available body size when that size is known. A request cannot include both
Content-Length and Transfer-Encoding. The only request transfer coding
Guzzle supports is a single chunked value on HTTP/1.1. Guzzle removes that
marker and adds an exact length when one is available. Otherwise, it lets the
transport choose valid wire framing. Other codings, coding chains, repeated
chunked values, unknown-length HTTP/1.0 bodies, and chunked on other HTTP
versions are rejected.
A Transfer-Encoding: chunked header is only a provisional framing marker:
Guzzle treats the request body as content, never as pre-encoded chunks. Callers
that pre-encoded bodies for Guzzle 7's stream handler must remove the chunk
framing before upgrading, or the server receives it as literal content.
For an unknown-size body with an explicit Content-Length, the stream handler
treats that length as the body boundary and does not read beyond it. Both
handlers reject premature EOF and request-body streams that return more bytes
than requested. The stream handler captures an otherwise unknown body once and
sends it with its exact length. Violations raise RequestException before a
response, or plain ResponseException if a cURL request-body read fails after
response headers. Omit both framing headers and let Guzzle choose.
PrepareBodyMiddleware now adds a provisional Transfer-Encoding: chunked
marker only to unknown-size HTTP/1.1 bodies. For other protocol versions, custom
handlers receive no provisional framing header.
A retry that reuses a consumed non-seekable body now fails if its known
remaining size no longer matches an explicit Content-Length. Use a seekable or
otherwise repeatable body for requests that may be retried.
Request Option Validation
Guzzle 8 rejects additional malformed request option values at the client
boundary. force_ip_resolve must be v4 or v6; protocols and
allow_redirects.protocols may contain only http and https; and delay
must be finite and non-negative. Per-request cookies values must be false
or a CookieJarInterface; the true shorthand is only valid in the client
constructor.
The following 33 options are now validated against their documented types before a transfer starts.
The boolean flags http_errors, stream, and synchronous must be bool.
decode_content and verify accept bool or string, expect accepts
bool or int, and debug accepts bool or a stream resource.
allow_redirects accepts bool or an array in which max is an int;
strict, referer, and track_redirects are bool; on_redirect is a
callable; and protocols is a non-empty array of http and https strings.
crypto_method, crypto_method_max, and retries must be int.
connect_timeout, read_timeout, and timeout must be int or float,
delay must be a finite, non-negative int or float, and version accepts
string, int, or float. cert_type and ssl_key_type must be string.
cert and ssl_key accept a string path or a [path, password] array
whose optional password may be a string or null. auth accepts false, a
string, or a [username, password] array of strings with an optional third
element that may be a string or null.
headers must be an array whose values are strings or non-empty arrays of
strings. form_params must be an array of strings, numbers, booleans, null,
or nested arrays of the same, and its floats must be finite. multipart must
be an array of part arrays, each with a string or int name, a contents
entry, optional string header values, and an optional string filename.
protocols must be a non-empty array containing only http and https.
curl and stream_context must be arrays.
on_headers, on_stats, on_trailers, and progress must be callables.
sink accepts a string path, a stream resource, or a StreamInterface, and
per-request cookies values must be false or a CookieJarInterface as
described above. Invalid values throw
GuzzleHttp\Exception\InvalidArgumentException naming the option and the
expected types.
A few build- or version-specific rejections that previously threw
InvalidArgumentException now throw RequestException, matching how the
HTTP-version capability checks already behave: requesting crypto_method TLS
1.3 on a libcurl built without it, and a stream-handler proxy whose raw
transport this PHP build's stream_get_transports() does not provide (for
example tls:// on a build without OpenSSL). The same input works on another
build, so it is a request failure rather than a caller error; a scheme invalid
on every build (such as udp:// or ftp:// for a proxy) still throws
InvalidArgumentException. Only code catching the specific exception type for
these build-misses needs to change.
Per-request Handler Option
Guzzle 7 deprecated the handler request option with a warning that Guzzle 8
would ignore request-level handlers. Guzzle 8 rejects the option instead:
passing handler in per-request options throws
GuzzleHttp\Exception\InvalidArgumentException before a transfer starts.
Silently ignoring the option would fail open — a test that supplied a
per-request MockHandler would fall through to the client's real handler and
hit the network. Configure the handler when creating the client, or use a
separate client instance for requests that need a different handler:
// Guzzle 7 (deprecated, used the request-level handler)
$client->request('GET', '/status', ['handler' => $mockHandler]);
// Guzzle 8
$client = new Client(['handler' => HandlerStack::create($mockHandler)]);
$client->request('GET', '/status');
Body Summaries In HTTP Error Exceptions
Guzzle's default http_errors middleware uses BodySummarizer to include a
short response body summary in response-aware exception messages. In Guzzle 7,
creating that summary rewound seekable response bodies to the beginning. In
Guzzle 8, BodySummarizer still summarizes from the beginning of the body, but
then restores the body cursor to its previous position.
Most applications do not need changes. Check only code that catches
response-aware exceptions from http_errors and then reads the response body
while relying on exception creation to leave that body rewound. If you need to
read the body from the beginning after catching the exception, call
Message::rewindBody() explicitly.
Request Protocol Versions
Invalid request protocol versions are no longer treated as omitted. Passing
'version' => '', 'version' => 'HTTP/1.1', or sending a PSR-7 request whose
protocol version is empty or malformed now fails before the request is sent.
Omit the version request option to use Guzzle's default HTTP/1.1 behavior, or
pass an explicit supported protocol version such as '1.1'. Do not include the
HTTP/ prefix.
Invalid version request option values are still rejected with
GuzzleHttp\Exception\InvalidArgumentException before a request is sent.
Empty or malformed protocol versions returned by a RequestInterface, and
well-formed but unsupported protocol versions such as HTTP/3 with the stream
handler, now fail with GuzzleHttp\Exception\RequestException. If you
previously caught InvalidArgumentException or ConnectException for request
protocol version failures, catch RequestException or GuzzleException
instead.
Callback Semantics
If you use the progress request option with the built-in cURL handlers, audit
callbacks for return values. Any truthy return value now aborts the transfer and
rejects the request with ResponseException when a response is available, or
RequestException otherwise. Return 0, false, or nothing to keep the
transfer running.
If a cURL progress callback throws, catch ResponseException for
response-aware failures or RequestException for broader request failures, and
inspect getPrevious() for the original throwable. The throwable no longer
escapes directly from the native cURL callback.
The stream handler still ignores progress return values.
Exceptions thrown by on_stats remain unwrapped, so existing catch logic for
on_stats exceptions does not need to change. The built-in cURL handlers now
release native easy handles before invoking on_stats. Built-in handlers now
reject non-callable on_stats values before starting the transfer, and the
built-in cURL handlers reject non-callable on_trailers values the same way.
Raw callbacks passed through the curl request option remain low-level cURL
callbacks and are not normalized by Guzzle.
The on_headers request option callback now receives the request as its second
argument. Existing userland callbacks that accept only the response continue to
work, but callbacks that inspect all arguments, for example with
func_get_args() or a variadic parameter, will observe the additional
Psr\Http\Message\RequestInterface argument.
The on_trailers request option callback now receives the request as its third
argument, after the trailer array and the response; callbacks that accept only
the first two arguments continue to work. Exceptions thrown by on_trailers
now reject the request with ResponseException instead of RequestException,
and the callback now runs before on_stats instead of after it, so on_stats
observes an on_trailers failure as the transfer's outcome.
When requests are sent through GuzzleHttp\Pool, the pool now appends the
request's iterable key as one extra trailing argument to any on_headers,
on_trailers, on_stats, progress, and allow_redirects.on_redirect
callbacks provided via its "options" configuration; callable requests yielded
to the pool likewise receive the wrapped callbacks in their options argument.
Existing userland callbacks that do not declare the extra parameter continue
to work unchanged, but callbacks that inspect all arguments, for example with
func_get_args() or a variadic parameter, will observe the additional
argument, and arity-strict PHP internal callables used directly may reject the
extra argument and should be wrapped in a userland callback. progress return
values are still honoured, and requests sent directly through a client are
unaffected.
The built-in handlers invoke on_headers for the final response headers and for
101 Switching Protocols, but not for other informational 1xx responses such
as 100 Continue or 103 Early Hints. Guzzle does not expose a separate Early
Hints API; a dedicated interim-response hook may be added in a future minor
release.
No-Content Response Bodies
The stream handler never reads the response body of a HEAD request, of a 1xx,
204, or 304 response, or of a 2xx response to a CONNECT request. Such responses
now always carry an empty body created by the configured stream_factory; the
transport connection is closed as soon as the headers (and the on_headers
callback) complete successfully; the sink option is not opened or written
(string path sinks create no file); and stream => true yields the empty stream
rather than the live transport stream. In particular, stream => true no longer
exposes the live transport for a 2xx CONNECT response. Per RFC 9110 these
responses cannot carry content, so any bytes a misbehaving server sends after
the header section are never read. The cURL handler already behaved this way via
libcurl.
HTTP/2 Multiplexing
The multiplex request option defaults to Multiplexing::WAIT:
HTTP/2 requests on the cURL handlers set libcurl's CURLOPT_PIPEWAIT, so a
concurrent burst waits for an in-progress connection it may be able to share
instead of dialing one connection per request. Guzzle 7 leaves this to libcurl,
which never waits by default. Pass
'multiplex' => Multiplexing::EAGER to restore the old dialing
behaviour, or Multiplexing::REQUIRE_EAGER/Multiplexing::REQUIRE_WAIT to
fail loudly unless a multiplexed protocol is guaranteed.
HTTP/2 requests also now require libcurl 7.65.2 or newer, so waiting is never silently unavailable where HTTP/2 works.
Sink Resource Ownership
PHP resources passed as the sink request option are no longer closed when the
response body is closed or garbage-collected by the built-in cURL and stream
handlers. Applications that relied on Guzzle closing a raw resource sink should
close the resource explicitly or pass a string path instead.
Stream Handler Header Serialization
The stream handler now trims only the trailing CRLF pair from the serialized header block it passes to the PHP stream context. Guzzle 7 also removed other trailing whitespace there, which could drop trailing spaces or horizontal tabs from the final header value when a request came from a PSR-7 implementation that does not trim header values.
TLS Minimum Version
The built-in cURL and stream handlers now default HTTPS requests to TLS 1.2 or
newer. Applications that must connect to legacy TLS 1.0 or TLS 1.1 endpoints can
explicitly lower the minimum version with the crypto_method request option:
$client->request('GET', 'https://legacy.example.com', [
'crypto_method' => STREAM_CRYPTO_METHOD_TLSv1_0_CLIENT,
]);
cURL Minimum Version
Guzzle 8 requires libcurl 7.34.0 or higher with SSL/TLS support when using the built-in cURL handlers. If this requirement is not met, the default handler stack will not select cURL automatically, and manually configured cURL handlers reject requests.
GuzzleHttp\Handler\Proxy::wrapTlsFallback() has been removed. Custom handler
stacks no longer need it; the default handler stack selects the cURL or stream
handler based on this TLS support automatically.
cURL Handler Lifecycle
Applications that manage built-in cURL handlers or factories directly should
treat closed instances as unusable. Reusing a closed CurlHandler,
CurlMultiHandler, or CurlFactory throws BadMethodCallException; create a
new instance instead.
If CurlMultiHandler::close() is called while transfers are pending, those
promises are rejected with GuzzleHttp\Exception\HandlerClosedException.
Destructor cleanup remains best-effort and does not reject pending promises.
A custom handle_factory passed to a built-in cURL handler remains
caller-owned. Closing the handler does not close an injected factory.
Timeout Option Validation
Built-in handlers validate timeout option values when they apply those options.
timeout is applied by both built-in transports. connect_timeout is applied
by cURL handlers and accepted without effect by the stream handler.
read_timeout is applied by the stream handler and accepted without effect by
cURL handlers. When a built-in handler applies a timeout option, positive
values below 0.001 seconds now throw InvalidArgumentException instead of
being converted to no timeout.
Stream Handler Timeout Enforcement
The stream handler now enforces the timeout request option as a total
transfer deadline when it buffers the response, matching the cURL handlers'
treatment of the option as the total time of the request. Once the deadline
passes while the response body is being buffered, the transfer is aborted and
the request is rejected with ResponseTimeoutException. Each body read is
bounded by the shorter of the read_timeout idle timeout and the remaining
total timeout. A response whose header block arrives in small pieces past the
deadline is rejected once the headers are complete, because PHP's HTTP stream
wrapper can only bound the time between packets while waiting for response
headers. In Guzzle 7, the stream handler applies timeout only while
connecting and as the idle time between packets, so a server that keeps
sending small pieces of data can extend a request past the configured timeout
indefinitely and still produce a successful response.
With the stream request option enabled, a response whose header block
completes past the deadline is rejected in the same way, and the response
carried by the exception has a closed body. The deadline does not apply to the
streamed response body, which is governed by the read_timeout idle timeout.
Stream Handler Timeout Model
The stream handler now treats timeout and read_timeout as orthogonal
controls. timeout is the total deadline for completing a buffered request or
obtaining a streamed response, with no deadline by default. read_timeout is
an idle timeout bounding how long the connection may sit silent at any stage,
while connecting, waiting for response headers, and between reads on the body,
defaulting to 60 seconds; 0 disables it. Any received data resets the idle
clock; nothing resets the deadline.
In Guzzle 7, both behaviours fall back to the default_socket_timeout ini
setting (60 seconds by default), timeout also acts as the idle cap, and
read_timeout only governs streamed reads. Set read_timeout explicitly to
tune or disable idle protection; default_socket_timeout no longer has any
effect on the stream handler.
Connect Timeout Default
The built-in cURL handlers now default the connect timeout to 60 seconds
instead of libcurl's 300, and connect_timeout set to 0 now disables the
connect timeout instead of selecting the 300-second libcurl default, matching
how 0 disables the timeout and read_timeout options. The stream handler
still accepts connect_timeout without effect; its connect phase is bounded
by the read_timeout idle timeout, which shares the 60-second default.
Stream Handler Header Injection
The stream handler no longer lets PHP's HTTP stream wrapper inject header
values from ini configuration. A request without a User-Agent header no longer
sends the user_agent ini value, and the from ini value is no longer sent:
the wrapper offers no way to omit the From header entirely, so deployments
that configure the ini send an empty From header instead of the configured
address. Both now match the cURL handlers, which ignore those ini settings.
Set the headers explicitly on the request or client to send them.
Proxy Option Validation
The proxy request option is validated more strictly. Proxy values must be
strings, and the proxy['no'] value may be either an array of strings or a
comma- or whitespace-delimited string such as the value from the NO_PROXY
environment variable. Other values now throw InvalidArgumentException.
Guzzle 7 skips invalid no entries instead of rejecting them.
When a request uses HTTP/3 and a proxy is resolved from the environment, the
request is now downgraded to HTTP/2 or HTTP/1.1 in the same way as for proxies
configured through the proxy request option, since current libcurl releases
cannot carry HTTP/3 over an HTTP proxy. A request excluded by the environment
no_proxy list stays direct and keeps HTTP/3.
No-Proxy Interpretation
No-proxy lists are interpreted identically everywhere they appear — the option's
no list, the client-mapped NO_PROXY environment variable, and the
environment no_proxy consulted by the built-in handlers — and the shared
interpretation follows libcurl's. Compared to Guzzle 7, this changes how string
lists are tokenized, how leading-dot entries match, and what a match means.
String no lists are split on whitespace as well as commas, and a leading-dot
entry such as .example.com now matches example.com as well as its
subdomains, exactly like the bare example.com entry. Both changes align the
option's matching with libcurl's interpretation of no_proxy, which the
environment path already followed. In Guzzle 7, the client mapping removes
spaces from NO_PROXY values, while the option form treats space-joined
values as a single entry that never matches and limits leading-dot entries to
subdomains.
A matching no entry now applies even when the array does not configure a proxy
for the request scheme: the request goes direct, and the built-in handlers do
not fall back to an environment proxy for it. The no list is also validated in
that case. Guzzle 7 ignores the no list entirely unless the array selects a
proxy for the request scheme.
Proxy URL Validation
The stream handler now throws InvalidArgumentException for any proxy URL
whose scheme it cannot execute: https://, SOCKS (socks4://, socks4a://,
socks5://, socks5h://), and anything other than http:// or a raw PHP
transport such as tcp://, ssl://, or tls://. Guzzle 7 passed these to
PHP, which failed later with an "unable to find the socket transport" error.
The cURL handlers reject a proxy URL whose scheme libcurl cannot use as a
proxy with the same exception. Raw PHP transport values still pass through the
stream wrapper unchanged.
Both handlers also reject a malformed proxy URL (an invalid host, an
out-of-range port, or leading junk before the scheme) up front with the same
InvalidArgumentException, instead of passing it to libcurl or PHP to fail on
later.
An http:// or scheme-less proxy that omits the port now defaults to 1080,
libcurl's default HTTP proxy port, so proxy.example.com resolves to
tcp://proxy.example.com:1080. Guzzle 7 passed a port-less proxy through
unchanged, which PHP's stream wrapper could not use because it requires an
explicit port.
Proxy Tunnels and HTTPS Proxies
Requests tunneled through an HTTP proxy, meaning an https:// target sent
through an http:// or https:// proxy, or a tunnel forced with raw curl
options, now require libcurl 7.54.0 or newer. HTTPS proxies (an https://
proxy URL, where the connection to the proxy itself is encrypted) also now
require libcurl 7.54.0 built with HTTPS-proxy support, raised from the 7.52.0
floor used in Guzzle 7. The built-in cURL handlers reject such requests on
older libcurl with a RequestException before they are sent, matching the
other build- and version-specific capability checks.
On supported libcurl, the proxy's CONNECT reply is suppressed with
CURLOPT_SUPPRESS_CONNECT_HEADERS. Guzzle 7 delivered the interim
200 Connection established header block to the on_headers callback and
could attach it to exceptions as a response. In Guzzle 8, on_headers
observes only origin responses, and a tunneled transfer failure is classified
by its transport phase instead of as a response failure carrying the proxy's
interim reply.
Proxy-Authorization Headers
In Guzzle 8, every first-class Proxy-Authorization field handled by a built-in
cURL handler requires libcurl 7.37.0 or newer with proxy header separation
support, regardless of Guzzle's predicted route. This includes an empty field,
which keeps its cURL header-control meaning in the proxy-only list, and requests
predicted to be direct, no-proxy-bypassed, or sent through SOCKS. A request is
rejected with a RequestException before network I/O when separation is not
available. Guzzle 7 instead omits the field on those known non-HTTP-proxy routes
and requires separation only when it cannot safely discard the field.
Guzzle 8 also rejects a raw CURLOPT_PROXYHEADER list when proxy header
separation is unavailable. Guzzle 7.15 only deprecates the raw option and passes
it through to the installed PHP cURL extension and libcurl.
When the stream handler selects a proxy, Guzzle 8 rejects any first-class
Proxy-Authorization field, including an empty value, because PHP streams have
no proxy-only header channel. Guzzle 7 instead accepts exactly one value,
rejects carriage returns and line feeds, and adds one canonical proxy
authorization line after selecting the proxy. That first-class value, including
an empty one, takes precedence over Basic credentials in proxy URL userinfo;
multiple values are rejected. Direct and no-proxy-bypassed stream requests omit
the field in both versions. Configure Basic proxy credentials in the proxy URI
or use a cURL handler for a first-class proxy authorization field.
Proxy Tunnels Under Shared Connection Caches
From libcurl 7.57.0, a cURL share handle passed directly to CurlFactory and
Guzzle's worker-global persistent sharing pools are treated as opaque
connection caches: anonymous HTTP and HTTPS proxy tunnels are forced onto
fresh, non-reusable connections, and TransportSharing::PERSISTENT_REQUIRE
rejects them. Guzzle 7 applies the same policy to its configured share handles
and additionally rejects the deprecated request-level CURLOPT_SHARE option
for proxy tunnels there; Guzzle 8 rejects that raw option outright, so only
the configured and persistent surfaces exist. Handler-lifetime
transport_sharing keeps ordinary tunnel reuse because its shares never lock
connections.
Proxy Environment Variable Resolution
The stream handler now resolves proxies from the environment the same way the
cURL handlers do. When the proxy request option makes no decision for a
request, it reads http_proxy (lowercase only), https_proxy/HTTPS_PROXY,
and all_proxy/ALL_PROXY, and consults no_proxy/NO_PROXY — including *
to disable proxying entirely. The uppercase HTTP_PROXY is never read (an
httpoxy defense), empty values are treated as unset, and on Windows proxy
environment variables are resolved only under the CLI SAPI. Guzzle 7's stream
handler never consulted the environment; it honored only the explicit proxy
option.
Because the stream handler cannot tunnel or speak TLS to a proxy, an
https_proxy or all_proxy set to an https:// or SOCKS URL now throws
InvalidArgumentException, exactly as the same value passed through the proxy
option already does, instead of connecting directly. An environment http://
proxy used for an "https" request still fails at connect time, because the
handler cannot open a CONNECT tunnel.
To ignore the proxy environment variables for a request or client, set the
proxy option to '': an explicit option decision is final, so this forces a
direct connection with no environment fallback. To send only selected hosts
directly, add them to the proxy option's no list, or list them in
no_proxy/NO_PROXY (* bypasses every host). These work identically on both
built-in handlers.
Handler-Specific Option Overrides
Handler-specific overrides remain available for finer transport control when
they do not conflict with Guzzle-managed behavior. The built-in cURL handlers
now reject raw cURL options that override request method, URI, body, headers,
timeouts, redirects, proxy URLs and types, TLS verification or client
credentials, progress/debug callbacks, sink handling, cookies, protocols,
connection coalescing, or cURL share handles. Use first-class Guzzle request
options for those settings. Allowed raw cURL header-list options, such as
CURLOPT_PROXYHEADER, now accept only strings or stringable objects as entries.
The cURL handlers also reject stream-only stream_context options, but accept
read_timeout without effect. The stream handler rejects cURL-only options it
cannot honor, but accepts connect_timeout without effect. These timeout options
are intentionally best-effort so shared request configuration can be reused
across transports.
Expect: 100-Continue Injection
Automatic Expect: 100-Continue injection now applies only to HTTP/1.1 requests.
HTTP/1.0, HTTP/2, and HTTP/3 requests do not receive the header from Guzzle's
body-preparation middleware.
The stream handler rejects an explicit expect option when it results in an
Expect header, because PHP streams do not support the 100-Continue workflow.
Set expect => false for stream-handler requests that must avoid this rejection.
Native Type Declarations
Guzzle 8 adds native parameter and return types where PHP 7.4 allows. Code overriding affected methods must update method signatures accordingly.
SetCookie::__toString() now returns string.
HandlerStack::remove() now throws TypeError when passed a value that is
neither a callable nor a middleware name string.
Pool::__construct() and Pool::batch() now require iterable request collections.
SetCookie::setSecure(), SetCookie::setDiscard(), and
SetCookie::setHttpOnly() now require boolean parameters. Calls from files that
declare strict types will throw TypeError for non-boolean values.
SetCookie::getExpires() now returns int|null. Invalid textual expiration
dates are treated as null.
IDN Conversion Option Types
The idn_conversion request option must be true, false, null, or an
integer IDNA_* bitmask. Numeric strings and floats that previously worked
through PHP scalar coercion are no longer accepted.
Integer 0 remains a valid option bitmask. Use false or null to disable IDN
conversion.
Generic Promise and Structured PHPDoc Types
Guzzle's async client APIs, handlers, and middleware callable annotations now use
generic PromiseInterface<ResponseInterface, mixed> PHPDoc types. This is a
static-analysis-only change at runtime, but projects with stricter static
analysis may see new or different diagnostics.
Code using unparameterized promise types continues to work. If your project implements Guzzle client interfaces, provides custom handlers or middleware, or uses stricter static analysis, you may need to update your PHPDoc annotations to include promise fulfillment and rejection types.
Public client config, request option, pool option, handler, middleware, mock
handler, and history middleware PHPDoc now uses structured array and callable
shapes. This does not change runtime behavior, but stricter static analysis may
now report invalid option keys, invalid option value types, or lower-arity
callback annotations that were previously hidden behind loose array or
callable PHPDoc.
If your project implements ClientInterface, extends client behavior through
traits, builds custom handlers or middleware, or documents reusable request
option arrays, update those PHPDoc annotations to match the supported request
option and callback shapes.
Multipart Request Serialization
Guzzle 8 uses Guzzle PSR-7 3.x for multipart request bodies. Multipart parts
created through the multipart request option no longer include generated
per-part Content-Length headers by default. If your tests compare raw
multipart payloads, remove those generated part headers from expected strings.
Generated multipart Content-Disposition header name and filename
parameters now escape double quotes, carriage returns, and line feeds as %22,
%0D, and %0A. Literal backslashes and other characters are serialized
unchanged, matching browser multipart form submission behavior. Custom multipart
part header names and values, and explicit PSR-7 multipart boundaries, are also
validated by PSR-7.
Custom multipart part header values also preserve trailing spaces and tabs in the serialized request body, so raw body snapshots or signatures may need updated expectations.
Guzzle now quotes the boundary parameter in generated
Content-Type: multipart/form-data headers when an explicit PSR-7
MultipartStream boundary contains characters that require quoting.
Automatically generated boundaries are unchanged.
You can still pass an explicit Content-Length header in a multipart element's
headers array if a non-standard peer requires it.
Cross-Origin Redirect Referer Header
With the optional referer redirect setting enabled (off by default), Guzzle
now sends only the request origin (scheme, host, and port) in the Referer
header on a cross-origin redirect. It previously sent the referring URI,
including the path, query string, and fragment, which could leak secrets such
as reset tokens or signed query parameters to the new origin.
Same-origin redirects still send the path and query, with any user information
and fragment removed. The Referer header is omitted entirely when the scheme
changes, including an https to http downgrade. This matches the
strict-origin-when-cross-origin policy that modern browsers use by default.
If you relied on the full URL crossing origins, collect it with the
on_redirect setting, or disable automatic redirects and follow them manually.
Automatic Redirects
Guzzle now follows only the redirect status codes 301, 302, 303, 307, and 308
when allow_redirects is enabled. Other 3xx responses, including 300, 304, 305,
and 306, are returned to the caller unchanged even when they carry a Location
header, in line with RFC 9110 section 15.4. Code that relied on Guzzle following
one of those responses should handle it directly or inspect it with an
on_redirect callback.
Secure Cookie Integrity
Guzzle 8 ignores Secure cookies received over non-HTTPS requests. It also
ignores an insecure cookie received over a non-HTTPS request when its name and
domain overlap an existing Secure cookie and its path falls within the
existing cookie's protected path. This includes deletions sent with
Max-Age=0.
Guzzle 7 accepted these cookies. Applications using plain HTTP development
servers that send Secure cookies must use HTTPS or remove the attribute.
Cookies inserted directly through CookieJar::setCookie() or restored from
persistence are not rejected because they have no response origin. Existing
Secure records still protect against later cookies received over HTTP.
Cookie Name Prefixes
CookieJar::extractCookies() now recognizes the __Secure- and __Host-
prefixes case-insensitively. __Secure- response cookies must be Secure.
__Host- response cookies must also be host-only, include a Path attribute,
and use the root path. A bare Path without = remains ignored. Invalid
prefixed response cookies are ignored.
Cookie names remain case-sensitive, so differently cased names remain distinct.
Correct the attributes or rename a legacy response cookie that used a reserved
prefix without satisfying its requirements. Cookies inserted directly through
CookieJar::setCookie() and restored persisted records are unchanged.
Host-Only Cookies
Cookies extracted from responses without a Domain attribute are now stored as
host-only cookies. They are sent only to the exact host that set them.
Previously, Guzzle stored these cookies with the request host as a normal domain
cookie, so they could also be sent to subdomains. Applications relying on that
behavior should use an explicit Domain attribute.
SetCookie::toArray() may include HostOnly => true for host-only cookies.
Cookie Domain Normalization
Cookie domains with multiple leading dots, such as Domain=..example.com, are
no longer normalized twice during matching. Such malformed domains are rejected
instead of matching example.com or its subdomains.
Cookie Max-Age Expiration
Cookies with Max-Age=0 or a negative Max-Age are now treated as immediately
expired. When such a cookie is added to a CookieJar, it removes a matching
stored cookie instead of being retained as a normal cookie.
SetCookie Constructor Field Validation
SetCookie constructor arrays no longer coerce invalid field values. Cookie
names, values, domains, paths, max-age values, expiry values, and boolean flags
must use the documented types. Invalid constructor values now throw
InvalidArgumentException.
// Valid:
new SetCookie([
'Name' => 'foo',
'Value' => 'bar',
'Domain' => 'example.com',
'Secure' => true,
]);
// Invalid in 8.0:
new SetCookie([
'Name' => false,
'Value' => 'bar',
]);
Cookies parsed from normal Set-Cookie headers continue to be normalized by
SetCookie::fromString(). Attributes that require a value (Domain, Path,
Expires, Max-Age) are ignored when they appear without one.
SetCookie Max-Age Precedence
SetCookie now follows RFC cookie precedence when both Max-Age and Expires
are present. A valid Max-Age value controls the effective expiration time,
including Max-Age=0 expiring the cookie immediately, even when Expires is a
future date.
Cookie Max-Age Parsing
SetCookie::fromString() now ignores float-like or exponent Max-Age values
such as 0.5, 1.5, or 1e3. Guzzle 7 truncated these numeric forms toward
zero. Use integer-second Max-Age values.
Set-Cookie Parsing Whitespace
SetCookie::fromString() now trims cookie names, cookie values, and attribute
segments with the RFC 6265 whitespace characters, space and horizontal tab.
Guzzle 7 also trimmed line feeds, carriage returns, null bytes, and vertical
tabs. Header values produced by Guzzle PSR-7 cannot contain those bytes, so
this only affects strings passed to SetCookie::fromString() directly.
CookieJar::clear Null Semantics
CookieJar::clear() now treats only null as an omitted path or name.
Previously, falsy path or name values such as '0' or '' could be interpreted
as omitted and clear a broader set of cookies than intended.
If you call clear() to clear all cookies, continue passing no arguments:
$jar->clear();
If you pass a path or name, that value is now treated as provided.
Cookie Name Lookup
CookieJar::getCookieByName() now matches cookie names case-sensitively. If a
jar contains distinct cookies such as SID and sid, retrieve each one with the
exact stored name.
Cookie Jar Persistence
FileCookieJar and SessionCookieJar can no longer be restored with
unserialize(); attempting to unserialize either jar now throws a
LogicException. Rebuild persisted jars instead: new FileCookieJar($path)
reloads the persisted cookie file when it exists, and
new SessionCookieJar($key) reloads the cookie data stored in the session.
FileCookieJar now writes its cookie file with owner-only permissions (0600),
so persisted cookies are not world-readable under the default umask; if another
user or process must read the file, adjust its permissions after saving. Saved
cookie files also JSON-escape tag characters without changing cookie values.
Persisted cookie data must now be a JSON list. Each list entry must decode to an
array, and recognized SetCookie fields must use their documented constructor
types. Malformed JSON, an invalid stored shape, or a recognized field with the
wrong type causes a RuntimeException. Cookie records are constructed before
any are passed to setCookie(), so such failures leave the jar unchanged.
Numeric or string-keyed JSON objects must be converted to lists.
Every cookie record must include an explicit boolean HostOnly marker. The
built-in jars write this marker automatically. Delete or rotate older nonempty
data without it, or annotate each record only when its original Domain
semantics are known.
In Guzzle 7, malformed JSON in a cookie file and FileCookieJar encoding
failures threw GuzzleHttp\Exception\InvalidArgumentException. Now they throw
RuntimeException, consistently with other persistent cookie failures. Both
jars expose the native JsonException through getPrevious() when JSON
encoding or decoding fails.
An empty cookie file remains a no-op. A missing or null session value still
means no stored cookie data. Any other session value must be a string containing
a JSON list; malformed JSON and an empty string are rejected. SessionCookieJar
does not replace the stored value when construction fails.
Logging Middleware Formatter Types
GuzzleHttp\MessageFormatter is now final. Applications that extended
MessageFormatter should implement GuzzleHttp\MessageFormatterInterface
instead and pass the custom formatter to GuzzleHttp\Middleware::log().
Middleware::log() now requires its formatter argument to implement
MessageFormatterInterface. Passing new MessageFormatter() still works
because MessageFormatter implements MessageFormatterInterface. Passing any
other value now fails with PHP's native TypeError instead of Guzzle's previous
LogicException.
use GuzzleHttp\MessageFormatterInterface;
use GuzzleHttp\Middleware;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
final class RedactingFormatter implements MessageFormatterInterface
{
public function format(
RequestInterface $request,
?ResponseInterface $response = null,
?\Throwable $error = null
): string {
return $request->getMethod().' '.$request->getUri()->getPath();
}
}
$stack->push(Middleware::log($logger, new RedactingFormatter()));
Built-in Handler Inheritance
GuzzleHttp\Handler\CurlFactory, GuzzleHttp\Handler\CurlHandler,
GuzzleHttp\Handler\CurlMultiHandler, GuzzleHttp\Handler\MockHandler, and
GuzzleHttp\Handler\StreamHandler are now final.
Applications that extended CurlFactory should implement
GuzzleHttp\Handler\CurlFactoryInterface instead. Applications that extended
CurlHandler, CurlMultiHandler, MockHandler, or StreamHandler should use
composition instead: wrap a handler instance in a custom callable or provide a
custom handler rather than subclassing the built-in handler.
Custom cURL Handle Factories
Custom GuzzleHttp\Handler\CurlFactoryInterface implementations that create or
mutate GuzzleHttp\Handler\EasyHandle instances must assign values compatible
with EasyHandle's documented public property types. Several EasyHandle
bookkeeping properties now use native property types, so assigning incompatible
values to those properties raises TypeError.
Custom factories must assign the request and sink state before returning an
EasyHandle to Guzzle because those properties are required typed invariants.
Reading them before assignment raises PHP's uninitialized typed-property Error.
The native cURL handle properties intentionally remain untyped because PHP 7.4
represents cURL handles as resources while PHP 8 represents them as cURL handle
objects. Custom factories must still unset $easy->handle when releasing an
easy handle, as required by CurlFactoryInterface::release().
Built-In Handler Constructor Options
CurlHandler, CurlMultiHandler, and StreamHandler now reject unknown
constructor option keys with GuzzleHttp\Exception\InvalidArgumentException.
Remove misspelled or application-specific keys before constructing built-in
handlers.
CurlMultiHandler now rejects cURL multi options that cannot be applied by the
installed runtime libcurl. Values passed through the constructor options key
must be an array keyed by integer CURLMOPT_* constants. Guzzle 7 already
fails closed when a named connection cap cannot be applied; Guzzle 8 extends
this rejection to every cURL multi option, including raw CURLMOPT_* entries
that Guzzle 7 only warns about.
CurlMultiHandler now rejects CURLMOPT_PIPELINING in the constructor
options array, deprecated since Guzzle 7.15. Pass Multiplexing::NONE as the
multiplex client option or, when constructing the handler yourself, as the
multiplex constructor option to disallow multiplexing on the handler
(replacing CURLPIPE_NOTHING and 0), or remove the option entirely for
multiplex-capable behavior (replacing masks containing CURLPIPE_MULTIPLEX);
multiplexing is on by default from libcurl 7.62, except on 7.65.0 and 7.65.1
where a regression dropped the default, and Guzzle 8's HTTP/2 floor of libcurl
7.65.2 is the version that restored it, so removing the option preserves
multiplex-capable behavior on every supported HTTP/2 runtime. CURLPIPE_HTTP1
and 1 also map to Multiplexing::NONE on libcurl 7.62 and newer, where
HTTP/1.1 pipelining is a no-op; on older libcurl they enabled HTTP/1.1
pipelining, which has no replacement. A handler configured with
Multiplexing::NONE wins over the default WAIT request mode: default requests
run without waiting, explicitly requested wait modes are rejected as a
configuration conflict when the transfer would actually wait, and the required
modes are always rejected. Multiplexing::NONE is also accepted as a request
option value exactly where its guarantee - the transfer does not share its
connection with any concurrent transfer - holds and can be verified; see the
multiplex request option documentation for the acceptance rules.
CurlMultiHandler now rejects CURLMOPT_MAX_HOST_CONNECTIONS and
CURLMOPT_MAX_TOTAL_CONNECTIONS entries in the constructor options array. Use
the max_host_connections and max_total_connections client options when
Guzzle creates the default handler, or the same named CurlMultiHandler
constructor options when constructing the handler directly.
Direct magic access to CurlMultiHandler::$_mh has been removed. This was an
undocumented internal lazy cURL multi handle. Applications that used it to set
CURLMOPT_* options should pass those values through the options key of the
CurlMultiHandler constructor.
Progress Callback Parameter Types
The built-in handlers now pass integer byte counts to progress callbacks.
Callbacks with int parameter types continue to work, and callbacks with
float parameter types can still receive integer byte counts in PHP. If a
callback used other scalar parameter types, update it to accept integers or
remove the scalar parameter declarations.
CurlMultiHandler Select Timeout
The GUZZLE_CURL_SELECT_TIMEOUT environment variable is no longer read. Pass
the select_timeout option to CurlMultiHandler instead. The
select_timeout option must be numeric, finite, and non-negative. It must be
0 or greater than or equal to 0.001 seconds.
Removed Client::__call and ClientInterface::getConfig
Client::__call() has been removed. The typed HTTP verb methods (get(),
head(), put(), post(), patch(), delete(), and their *Async()
variants) have been real methods since Guzzle 7.0 and are unaffected. Only
verbs without a typed method lose their magic form: calls such as
$client->options($uri), $client->trace($uri),
$client->optionsAsync($uri), or any custom verb such as
$client->purge($uri) now fail with a PHP undefined method error. Use
request() or requestAsync() with an explicit method instead:
// 7.x
$response = $client->options('http://example.com');
// 8.0
$response = $client->request('OPTIONS', 'http://example.com');
ClientInterface::getConfig() has been removed from the interface. The
concrete Client::getConfig() method remains available and is no longer
deprecated. Code reading configuration through a ClientInterface-typed
value must type against Client instead, and custom ClientInterface
implementations no longer need to provide getConfig(), although keeping the
method still satisfies the interface.
Removed Function and JSON Helper APIs
The deprecated GuzzleHttp namespace functions were removed, along with the
functions.php and functions_include.php files and the Composer files
autoload entry that loaded them on every request.
Replace namespaced function calls with their native or class equivalents:
// Before:
use function GuzzleHttp\json_decode;
$data = json_decode($json);
// After:
$data = \json_decode($json, false, 512, \JSON_THROW_ON_ERROR);
| Original Function | Replacement |
|---|---|
describe_type |
PHP's get_debug_type |
headers_from_lines |
Utils::headersFromLines |
debug_resource |
Utils::debugResource |
choose_handler |
Utils::chooseHandler |
default_user_agent |
Utils::defaultUserAgent |
default_ca_bundle |
none; use the system trust store or verify |
normalize_header_keys |
Utils::normalizeHeaderKeys |
is_host_in_noproxy |
ProxyOptions::isHostInNoProxy |
json_decode |
PHP's json_decode with JSON_THROW_ON_ERROR |
json_encode |
PHP's json_encode with JSON_THROW_ON_ERROR |
The deprecated Utils::jsonDecode() and Utils::jsonEncode() methods have
also been removed. Use PHP's native JSON functions with
JSON_THROW_ON_ERROR. Failures throw JsonException directly.
See the "Removed Proxy Helper API" section below for is_host_in_noproxy()
behavior differences. The deprecated Utils::defaultCaBundle() and the
internal Utils::isUriInNoProxy() helpers have also been removed; use the
verify option and ProxyOptions::isUriInNoProxy() respectively.
Removed Middleware Helper APIs
RetryMiddleware::exponentialDelay() has been removed. The retry middleware
continues to use the same exponential backoff calculation by default. This only
affects code that called the static helper directly; inline that calculation or
pass a custom delay callable to Middleware::retry().
RedirectMiddleware::$defaultSettings has been removed. Use
RedirectMiddleware::DEFAULT_SETTINGS instead.
Removed Proxy Helper API
Utils::isHostInNoProxy() has been removed.
Use ProxyOptions::resolve() when implementing Guzzle-compatible proxy handling
in a custom handler. Use ProxyOptions::isUriInNoProxy() when checking whether
a request URI matches a no-proxy list. Use ProxyOptions::isHostInNoProxy()
only when checking a host string directly. The environment-variable fallback
performed by the built-in handlers is not part of ProxyOptions::resolve();
custom handlers that want it must implement their own environment lookup.
These helpers share the normalized no-proxy matching of the Guzzle 7 Utils
helpers: domain matching is case-insensitive and ignores a single trailing DNS
root dot, IP literals are normalized before comparison, and CIDR entries match
IP literal hosts. They differ from the Guzzle 7 helpers in three ways — they
throw InvalidArgumentException for non-string list entries where Guzzle 7
skips them, a leading-dot entry such as .example.com also matches the bare
domain, and string no-proxy lists are split on whitespace as well as commas.
Removed Type Description Helper API
Utils::describeType() has been removed. Use PHP's get_debug_type() instead.
Non-instantiable Utility Classes
Static utility and constant classes such as GuzzleHttp\Middleware,
GuzzleHttp\Utils, GuzzleHttp\RequestOptions,
GuzzleHttp\Handler\HeaderProcessor, and GuzzleHttp\Handler\Proxy now have
private constructors. GuzzleHttp\Handler\Proxy is also declared final.
These classes only expose static members. Replace any accidental instantiation with static method calls or constant access.
Removed HandlerStack String Dump
HandlerStack::__toString() has been removed.
Native PHP Serialization of Runtime Objects
Runtime objects such as clients, handler stacks, middleware, handlers, pools,
mock handlers, cURL transport objects, and persistent cookie jars no longer
support native PHP serialize() or unserialize(). Persist configuration or
cookie data explicitly and rebuild runtime objects during bootstrap.
Sensitive Stack Trace Arguments
Guzzle 8 marks credential-bearing parameters with SensitiveParameter. On PHP
8.2 and later, selected exception-trace arguments are represented by
SensitiveParameterValue instead of exposing the original argument. PHP 7.4
through 8.1 do not redact trace arguments. The attribute does not redact logs,
exception messages, HTTP traffic, properties, captured variables, return
values, custom callback frames, or the separate $this/object entry in
explicit backtraces. PHP 8.2 also does not reliably redact named values
collected by an attributed variadic; PHP 8.3+ does.
6.0 to 7.0
In order to take advantage of the new features of PHP, Guzzle dropped the support of PHP 5. The minimum supported PHP version is now PHP 7.2. Type hints and return types for functions and methods have been added wherever possible.
Please make sure:
- You are calling a function or a method with the correct type.
- If you extend a class of Guzzle; update all signatures on methods you override.
Other Backwards Compatibility Breaking Changes
- Class
GuzzleHttp\UriTemplateis removed. - Class
GuzzleHttp\Exception\SeekExceptionis removed. - Classes
GuzzleHttp\Exception\BadResponseException,GuzzleHttp\Exception\ClientException,GuzzleHttp\Exception\ServerExceptioncan no longer be initialized with an empty Response as argument. - Class
GuzzleHttp\Exception\ConnectExceptionnow extendsGuzzleHttp\Exception\TransferExceptioninstead ofGuzzleHttp\Exception\RequestException. - Function
GuzzleHttp\Exception\ConnectException::getResponse()is removed. - Function
GuzzleHttp\Exception\ConnectException::hasResponse()is removed. - Constant
GuzzleHttp\ClientInterface::VERSIONis removed. AddedGuzzleHttp\ClientInterface::MAJOR_VERSIONinstead. - Function
GuzzleHttp\Exception\RequestException::getResponseBodySummaryis removed. Use\GuzzleHttp\Psr7\get_message_body_summaryas an alternative. - Function
GuzzleHttp\Cookie\CookieJar::getCookieValueis removed. - Request option
exceptionsis removed. Please usehttp_errors. - Request option
save_tois removed. Please usesink. - Pool option
pool_sizeis removed. Please useconcurrency. - We now look for environment variables in the
$_SERVERsuper global, due to thread safety issues withgetenv. We continue to fallback togetenvin CLI environments, for maximum compatibility. - The
get,head,put,post,patch,delete,getAsync,headAsync,putAsync,postAsync,patchAsync, anddeleteAsyncmethods are now implemented as genuine methods onGuzzleHttp\Client, with strong typing. The original__callimplementation remains unchanged for now, for maximum backwards compatibility, but won't be invoked under normal operation. - The
logmiddleware will log the errors with levelerrorinstead ofnotice - Support for international domain names (IDN) is now disabled by default, and enabling it requires installing ext-intl, linked against a modern version of the C library (ICU 4.6 or higher).
Native Functions Calls
All internal native functions calls of Guzzle are now prefixed with a slash. This change makes it impossible for method overloading by other libraries or applications. Example:
// Before:
curl_version();
// After:
\curl_version();
For the full diff you can check here.
5.0 to 6.0
Guzzle now uses PSR-7 for HTTP messages.
Due to the fact that these messages are immutable, this prompted a refactoring
of Guzzle to use a middleware based system rather than an event system. Any
HTTP message interaction (e.g., GuzzleHttp\Message\Request) need to be
updated to work with the new immutable PSR-7 request and response objects. Any
event listeners or subscribers need to be updated to become middleware
functions that wrap handlers (or are injected into a
GuzzleHttp\HandlerStack).
- Removed
GuzzleHttp\BatchResults - Removed
GuzzleHttp\Collection - Removed
GuzzleHttp\HasDataTrait - Removed
GuzzleHttp\ToArrayInterface - The
guzzlehttp/streamsdependency has been removed. Stream functionality is now present in theGuzzleHttp\Psr7namespace provided by theguzzlehttp/psr7package. - Guzzle no longer uses ReactPHP promises and now uses the
guzzlehttp/promiseslibrary. We use a custom promise library for three significant reasons:- React promises (at the time of writing this) are recursive. Promise chaining and promise resolution will eventually blow the stack. Guzzle promises are not recursive as they use a sort of trampolining technique. Note: there has been movement in the React project to modify promises to no longer utilize recursion.
- Guzzle needs to have the ability to synchronously block on a promise to wait for a result. Guzzle promises allows this functionality (and does not require the use of recursion).
- Because we need to be able to wait on a result, doing so using React promises requires wrapping react promises with RingPHP futures. This overhead is no longer needed, reducing stack sizes, reducing complexity, and improving performance.
GuzzleHttp\Mimetypeshas been moved to a function inGuzzleHttp\Psr7\mimetype_from_extensionandGuzzleHttp\Psr7\mimetype_from_filename.GuzzleHttp\QueryandGuzzleHttp\QueryParserhave been removed. Query strings must now be passed into request objects as strings, or provided to thequeryrequest option when creating requests with clients. Thequeryoption uses PHP'shttp_build_queryto convert an array to a string. If you need a different serialization technique, you will need to pass the query string in as a string. There are a couple helper functions that will make working with query strings easier:GuzzleHttp\Psr7\parse_queryandGuzzleHttp\Psr7\build_query.- Guzzle no longer has a dependency on RingPHP. Due to the use of a middleware
system based on PSR-7, using RingPHP and it's middleware system as well adds
more complexity than the benefits it provides. All HTTP handlers that were
present in RingPHP have been modified to work directly with PSR-7 messages
and placed in the
GuzzleHttp\Handlernamespace. This significantly reduces complexity in Guzzle, removes a dependency, and improves performance. RingPHP will be maintained for Guzzle 5 support, but will no longer be a part of Guzzle 6. - As Guzzle now uses a middleware based systems the event system and RingPHP
integration has been removed. Note: while the event system has been removed,
it is possible to add your own type of event system that is powered by the
middleware system.
- Removed the
Eventnamespace. - Removed the
Subscribernamespace. - Removed
Transactionclass - Removed
RequestFsm - Removed
RingBridge GuzzleHttp\Subscriber\Cookieis now provided byGuzzleHttp\Middleware::cookiesGuzzleHttp\Subscriber\HttpErroris now provided byGuzzleHttp\Middleware::httpErrorGuzzleHttp\Subscriber\Historyis now provided byGuzzleHttp\Middleware::historyGuzzleHttp\Subscriber\Mockis now provided byGuzzleHttp\Handler\MockHandlerGuzzleHttp\Subscriber\Prepareis now provided byGuzzleHttp\PrepareBodyMiddlewareGuzzleHttp\Subscriber\Redirectis now provided byGuzzleHttp\RedirectMiddleware
- Removed the
- Guzzle now uses
Psr\Http\Message\UriInterface(implements inGuzzleHttp\Psr7\Uri) for URI support.GuzzleHttp\Urlis now gone. - Static functions in
GuzzleHttp\Utilshave been moved to namespaced functions under theGuzzleHttpnamespace. This requires either a Composer based autoloader or you to include functions.php. GuzzleHttp\ClientInterface::getDefaultOptionhas been renamed toGuzzleHttp\ClientInterface::getConfig.GuzzleHttp\ClientInterface::setDefaultOptionhas been removed.- The
jsonandxmlmethods of response objects has been removed. With the migration to strictly adhering to PSR-7 as the interface for Guzzle messages, adding methods to message interfaces would actually require Guzzle messages to extend from PSR-7 messages rather then work with them directly.
Migrating to middleware
The change to PSR-7 unfortunately required significant refactoring to Guzzle due to the fact that PSR-7 messages are immutable. Guzzle 5 relied on an event system from plugins. The event system relied on mutability of HTTP messages and side effects in order to work. With immutable messages, you have to change your workflow to become more about either returning a value (e.g., functional middlewares) or setting a value on an object. Guzzle v6 has chosen the functional middleware approach.
Instead of using the event system to listen for things like the before event,
you now create a stack based middleware function that intercepts a request on
the way in and the promise of the response on the way out. This is a much
simpler and more predictable approach than the event system and works nicely
with PSR-7 middleware. Due to the use of promises, the middleware system is
also asynchronous.
v5:
use GuzzleHttp\Event\BeforeEvent;
$client = new GuzzleHttp\Client();
// Get the emitter and listen to the before event.
$client->getEmitter()->on('before', function (BeforeEvent $e) {
// Guzzle v5 events relied on mutation
$e->getRequest()->setHeader('X-Foo', 'Bar');
});
v6:
In v6, you can modify the request before it is sent using the mapRequest
middleware. The idiomatic way in v6 to modify the request/response lifecycle is
to setup a handler middleware stack up front and inject the handler into a
client.
use GuzzleHttp\Middleware;
// Create a handler stack that has all of the default middlewares attached
$handler = GuzzleHttp\HandlerStack::create();
// Push the handler onto the handler stack
$handler->push(Middleware::mapRequest(function (RequestInterface $request) {
// Notice that we have to return a request object
return $request->withHeader('X-Foo', 'Bar');
}));
// Inject the handler into the client
$client = new GuzzleHttp\Client(['handler' => $handler]);
POST Requests
This version added the form_params
and multipart request options. form_params is an associative array of
strings or array of strings and is used to serialize an
application/x-www-form-urlencoded POST request. The
multipart
option is now used to send a multipart/form-data POST request.
GuzzleHttp\Post\PostFile has been removed. Use the multipart option to add
POST files to a multipart/form-data request.
The body option no longer accepts an array to send POST requests. Please use
multipart or form_params instead.
The base_url option has been renamed to base_uri.
4.x to 5.0
Rewritten Adapter Layer
Guzzle now uses RingPHP to send
HTTP requests. The adapter option in a GuzzleHttp\Client constructor
is still supported, but it has now been renamed to handler. Instead of
passing a GuzzleHttp\Adapter\AdapterInterface, you must now pass a PHP
callable that follows the RingPHP specification.
Removed Fluent Interfaces
Fluent interfaces were removed from the following classes:
GuzzleHttp\CollectionGuzzleHttp\UrlGuzzleHttp\QueryGuzzleHttp\Post\PostBodyGuzzleHttp\Cookie\SetCookie
Removed functions.php
Removed "functions.php", so that Guzzle is truly PSR-4 compliant. The following functions can be used as replacements.
GuzzleHttp\json_decode-> PHP'sjson_decodeGuzzleHttp\get_path->GuzzleHttp\Utils::getPathGuzzleHttp\Utils::setPath->GuzzleHttp\set_pathGuzzleHttp\Pool::batch->GuzzleHttp\batch. This function is, however, deprecated in favor of usingGuzzleHttp\Pool::batch().
The "procedural" global client has been removed with no replacement (e.g.,
GuzzleHttp\get(), GuzzleHttp\post(), etc.). Use a GuzzleHttp\Client
object as a replacement.
throwImmediately has been removed
The concept of "throwImmediately" has been removed from exceptions and error events. This control mechanism was used to stop a transfer of concurrent requests from completing. This can now be handled by throwing the exception or by cancelling a pool of requests or each outstanding future request individually.
headers event has been removed
Removed the "headers" event. This event was only useful for changing the body a response once the headers of the response were known. You can implement a similar behavior in a number of ways. One example might be to use a FnStream that has access to the transaction being sent. For example, when the first byte is written, you could check if the response headers match your expectations, and if so, change the actual stream body that is being written to.
Updates to HTTP Messages
Removed the asArray parameter from
GuzzleHttp\Message\MessageInterface::getHeader. If you want to get a header
value as an array, then use the newly added getHeaderAsArray() method of
MessageInterface. This change makes the Guzzle interfaces compatible with
the PSR-7 interfaces.
3.x to 4.0
Overarching changes:
- Now requires PHP 5.4 or greater.
- No longer requires cURL to send requests.
- Guzzle no longer wraps every exception it throws. Only exceptions that are recoverable are now wrapped by Guzzle.
- Various namespaces have been removed or renamed.
- No longer requiring the Symfony EventDispatcher. A custom event dispatcher
based on the Symfony EventDispatcher is
now utilized in
GuzzleHttp\Event\EmitterInterface(resulting in significant speed and functionality improvements).
Changes per Guzzle 3.x namespace are described below.
Batch
The Guzzle\Batch namespace has been removed. This is best left to
third-parties to implement on top of Guzzle's core HTTP library.
Cache
The Guzzle\Cache namespace has been removed. (Todo: No suitable replacement
has been implemented yet, but hoping to utilize a PSR cache interface).
Common
- Removed all of the wrapped exceptions. It's better to use the standard PHP library for unrecoverable exceptions.
FromConfigInterfacehas been removed.Guzzle\Common\Versionhas been removed. The VERSION constant can be found atGuzzleHttp\ClientInterface::VERSION.
Collection
getAllhas been removed. UsetoArrayto convert a collection to an array.injecthas been removed.keySearchhas been removed.getPathno longer supports wildcard expressions. Use something better like JMESPath for this.setPathnow supports appending to an existing array via the[]notation.
Events
Guzzle no longer requires Symfony's EventDispatcher component. Guzzle now uses
GuzzleHttp\Event\Emitter.
Symfony\Component\EventDispatcher\EventDispatcherInterfaceis replaced byGuzzleHttp\Event\EmitterInterface.Symfony\Component\EventDispatcher\EventDispatcheris replaced byGuzzleHttp\Event\Emitter.Symfony\Component\EventDispatcher\Eventis replaced byGuzzleHttp\Event\Event, and Guzzle now has an EventInterface inGuzzleHttp\Event\EventInterface.AbstractHasDispatcherhas moved to a trait,HasEmitterTrait, andHasDispatcherInterfacehas moved toHasEmitterInterface. Retrieving the event emitter of a request, client, etc. now uses thegetEmittermethod rather than thegetDispatchermethod.
Emitter
- Use the
once()method to add a listener that automatically removes itself the first time it is invoked. - Use the
listeners()method to retrieve a list of event listeners rather than thegetListeners()method. - Use
emit()instead ofdispatch()to emit an event from an emitter. - Use
attach()instead ofaddSubscriber()anddetach()instead ofremoveSubscriber().
$mock = new Mock();
// 3.x
$request->getEventDispatcher()->addSubscriber($mock);
$request->getEventDispatcher()->removeSubscriber($mock);
// 4.x
$request->getEmitter()->attach($mock);
$request->getEmitter()->detach($mock);
Use the on() method to add a listener rather than the addListener() method.
// 3.x
$request->getEventDispatcher()->addListener('foo', function (Event $event) { /* ... */ } );
// 4.x
$request->getEmitter()->on('foo', function (Event $event, $name) { /* ... */ } );
Http
General changes
- The cacert.pem certificate has been moved to
src/cacert.pem. - Added the concept of adapters that are used to transfer requests over the wire.
- Simplified the event system.
- Sending requests in parallel is still possible, but batching is no longer a
concept of the HTTP layer. Instead, you must use the
completeanderrorevents to asynchronously manage parallel request transfers. Guzzle\Http\Urlhas moved toGuzzleHttp\Url.Guzzle\Http\QueryStringhas moved toGuzzleHttp\Query.- QueryAggregators have been rewritten so that they are simply callable functions.
GuzzleHttp\StaticClienthas been removed. Use the functions provided infunctions.phpfor an easy to use static client instance.- Exceptions in
GuzzleHttp\Exceptionhave been updated to all extend fromGuzzleHttp\Exception\TransferException.
Client
Calling methods like get(), post(), head(), etc. no longer create and
return a request, but rather creates a request, sends the request, and returns
the response.
// 3.0
$request = $client->get('/');
$response = $request->send();
// 4.0
$response = $client->get('/');
// or, to mirror the previous behavior
$request = $client->createRequest('GET', '/');
$response = $client->send($request);
GuzzleHttp\ClientInterface has changed.
- The
sendmethod no longer accepts more than one request. UsesendAllto send multiple requests in parallel. setUserAgent()has been removed. Use a default request option instead. You could, for example, do something like:$client->setConfig('defaults/headers/User-Agent', 'Foo/Bar ' . $client::getDefaultUserAgent()).setSslVerification()has been removed. Use default request options instead, like$client->setConfig('defaults/verify', true).
GuzzleHttp\Client has changed.
- The constructor now accepts only an associative array. You can include a
base_urlstring or array to use a URI template as the base URL of a client. You can also specify adefaultskey that is an associative array of default request options. You can pass anadapterto use a custom adapter,batch_adapterto use a custom adapter for sending requests in parallel, or amessage_factoryto change the factory used to create HTTP requests and responses. - The client no longer emits a
client.create_requestevent. - Creating requests with a client no longer automatically utilize a URI
template. You must pass an array into a creational method (e.g.,
createRequest,get,put, etc.) in order to expand a URI template.
Messages
Messages no longer have references to their counterparts (i.e., a request no
longer has a reference to it's response, and a response no loger has a
reference to its request). This association is now managed through a
GuzzleHttp\Adapter\TransactionInterface object. You can get references to
these transaction objects using request events that are emitted over the
lifecycle of a request.
Requests with a body
GuzzleHttp\Message\EntityEnclosingRequestandGuzzleHttp\Message\EntityEnclosingRequestInterfacehave been removed. The separation between requests that contain a body and requests that do not contain a body has been removed, and nowGuzzleHttp\Message\RequestInterfacehandles both use cases.- Any method that previously accepts a
GuzzleHttp\Responseobject now accept aGuzzleHttp\Message\ResponseInterface. GuzzleHttp\Message\RequestFactoryInterfacehas been renamed toGuzzleHttp\Message\MessageFactoryInterface. This interface is used to create both requests and responses and is implemented inGuzzleHttp\Message\MessageFactory.- POST field and file methods have been removed from the request object. You
must now use the methods made available to
GuzzleHttp\Post\PostBodyInterfaceto control the format of a POST body. Requests that are created using a standardGuzzleHttp\Message\MessageFactoryInterfacewill automatically use aGuzzleHttp\Post\PostBodybody if the body was passed as an array or if the method is POST and no body is provided.
$request = $client->createRequest('POST', '/');
$request->getBody()->setField('foo', 'bar');
$request->getBody()->addFile(new PostFile('file_key', fopen('/path/to/content', 'r')));
Headers
GuzzleHttp\Message\Headerhas been removed. Header values are now simply represented by an array of values or as a string. Header values are returned as a string by default when retrieving a header value from a message. You can pass an optional argument oftrueto retrieve a header value as an array of strings instead of a single concatenated string.GuzzleHttp\PostFileandGuzzleHttp\PostFileInterfacehave been moved toGuzzleHttp\Post. This interface has been simplified and now allows the addition of arbitrary headers.- Custom headers like
GuzzleHttp\Message\Header\Linkhave been removed. Most of the custom headers are now handled separately in specific subscribers/plugins, andGuzzleHttp\Message\HeaderValues::parseParams()has been updated to properly handle headers that contain parameters (like theLinkheader).
Responses
GuzzleHttp\Message\Response::getInfo()andGuzzleHttp\Message\Response::setInfo()have been removed. Use the event system to retrieve this type of information.GuzzleHttp\Message\Response::getRawHeaders()has been removed.GuzzleHttp\Message\Response::getMessage()has been removed.GuzzleHttp\Message\Response::calculateAge()and other cache specific methods have moved to the CacheSubscriber.- Header specific helper functions like
getContentMd5()have been removed. Just usegetHeader('Content-MD5')instead. GuzzleHttp\Message\Response::setRequest()andGuzzleHttp\Message\Response::getRequest()have been removed. Use the event system to work with request and response objects as a transaction.GuzzleHttp\Message\Response::getRedirectCount()has been removed. Use the Redirect subscriber instead.GuzzleHttp\Message\Response::isSuccessful()and other related methods have been removed. UsegetStatusCode()instead.
Streaming responses
Streaming requests can now be created by a client directly, returning a
GuzzleHttp\Message\ResponseInterface object that contains a body stream
referencing an open PHP HTTP stream.
// 3.0
use Guzzle\Stream\PhpStreamRequestFactory;
$request = $client->get('/');
$factory = new PhpStreamRequestFactory();
$stream = $factory->fromRequest($request);
$data = $stream->read(1024);
// 4.0
$response = $client->get('/', ['stream' => true]);
// Read some data off of the stream in the response body
$data = $response->getBody()->read(1024);
Redirects
The configureRedirects() method has been removed in favor of a
allow_redirects request option.
// Standard redirects with a default of a max of 5 redirects
$request = $client->createRequest('GET', '/', ['allow_redirects' => true]);
// Strict redirects with a custom number of redirects
$request = $client->createRequest('GET', '/', [
'allow_redirects' => ['max' => 5, 'strict' => true]
]);
EntityBody
EntityBody interfaces and classes have been removed or moved to
GuzzleHttp\Stream. All classes and interfaces that once required
GuzzleHttp\EntityBodyInterface now require
GuzzleHttp\Stream\StreamInterface. Creating a new body for a request no
longer uses GuzzleHttp\EntityBody::factory but now uses
GuzzleHttp\Stream\Stream::factory or even better:
GuzzleHttp\Stream\create().
Guzzle\Http\EntityBodyInterfaceis nowGuzzleHttp\Stream\StreamInterfaceGuzzle\Http\EntityBodyis nowGuzzleHttp\Stream\StreamGuzzle\Http\CachingEntityBodyis nowGuzzleHttp\Stream\CachingStreamGuzzle\Http\ReadLimitEntityBodyis nowGuzzleHttp\Stream\LimitStreamGuzzle\Http\IoEmittyinEntityBodyhas been removed.
Request lifecycle events
Requests previously submitted a large number of requests. The number of events
emitted over the lifecycle of a request has been significantly reduced to make
it easier to understand how to extend the behavior of a request. All events
emitted during the lifecycle of a request now emit a custom
GuzzleHttp\Event\EventInterface object that contains context providing
methods and a way in which to modify the transaction at that specific point in
time (e.g., intercept the request and set a response on the transaction).
request.before_sendhas been renamed tobeforeand now emits aGuzzleHttp\Event\BeforeEventrequest.completehas been renamed tocompleteand now emits aGuzzleHttp\Event\CompleteEvent.request.senthas been removed. Usecomplete.request.successhas been removed. Usecomplete.erroris now an event that emits aGuzzleHttp\Event\ErrorEvent.request.exceptionhas been removed. Useerror.request.receive.status_linehas been removed.curl.callback.progresshas been removed. Use a customStreamInterfaceto maintain a status update.curl.callback.writehas been removed. Use a customStreamInterfaceto intercept writes.curl.callback.readhas been removed. Use a customStreamInterfaceto intercept reads.
headers is a new event that is emitted after the response headers of a
request have been received before the body of the response is downloaded. This
event emits a GuzzleHttp\Event\HeadersEvent.
You can intercept a request and inject a response using the intercept() event
of a GuzzleHttp\Event\BeforeEvent, GuzzleHttp\Event\CompleteEvent, and
GuzzleHttp\Event\ErrorEvent event.
Inflection
The Guzzle\Inflection namespace has been removed. This is not a core concern
of Guzzle.
Iterator
The Guzzle\Iterator namespace has been removed.
Guzzle\Iterator\AppendIterator,Guzzle\Iterator\ChunkedIterator, andGuzzle\Iterator\MethodProxyIteratorare nice, but not a core requirement of Guzzle itself.Guzzle\Iterator\FilterIteratoris no longer needed because an equivalent class is shipped with PHP 5.4.Guzzle\Iterator\MapIteratoris not really needed when using PHP 5.5 because it's easier to just wrap an iterator in a generator that maps values.
For a replacement of these iterators, see https://github.com/nikic/iter
Log
The LogPlugin has moved to https://github.com/guzzle/log-subscriber. The
Guzzle\Log namespace has been removed. Guzzle now relies on
Psr\Log\LoggerInterface for all logging. The MessageFormatter class has been
moved to GuzzleHttp\Subscriber\Log\Formatter.
Parser
The Guzzle\Parser namespace has been removed. This was previously used to
make it possible to plug in custom parsers for cookies, messages, URI
templates, and URLs; however, this level of complexity is not needed in Guzzle
so it has been removed.
- Cookie: Cookie parsing logic has been moved to
GuzzleHttp\Cookie\SetCookie::fromString. - Message: Message parsing logic for both requests and responses has been moved
to
GuzzleHttp\Message\MessageFactory::fromMessage. Message parsing is only used in debugging or deserializing messages, so it doesn't make sense for Guzzle as a library to add this level of complexity to parsing messages. - UriTemplate: URI template parsing has been moved to
GuzzleHttp\UriTemplate. The Guzzle library will automatically use the PECL URI template library if it is installed. - Url: URL parsing is now performed in
GuzzleHttp\Url::fromString(previously it wasGuzzle\Http\Url::factory()). If custom URL parsing is necessary, then developers are free to subclassGuzzleHttp\Url.
Plugin
The Guzzle\Plugin namespace has been renamed to GuzzleHttp\Subscriber.
Several plugins are shipping with the core Guzzle library under this namespace.
GuzzleHttp\Subscriber\Cookie: Replaces the old CookiePlugin. Cookie jar code has moved toGuzzleHttp\Cookie.GuzzleHttp\Subscriber\History: Replaces the old HistoryPlugin.GuzzleHttp\Subscriber\HttpError: Throws errors when a bad HTTP response is received.GuzzleHttp\Subscriber\Mock: Replaces the old MockPlugin.GuzzleHttp\Subscriber\Prepare: Prepares the body of a request just before sending. This subscriber is attached to all requests by default.GuzzleHttp\Subscriber\Redirect: Replaces the RedirectPlugin.
The following plugins have been removed (third-parties are free to re-implement these if needed):
GuzzleHttp\Plugin\Asynchas been removed.GuzzleHttp\Plugin\CurlAuthhas been removed.GuzzleHttp\Plugin\ErrorResponse\ErrorResponsePluginhas been removed. This functionality should instead be implemented with event listeners that occur after normal response parsing occurs in the guzzle/command package.
The following plugins are not part of the core Guzzle package, but are provided in separate repositories:
Guzzle\Http\Plugin\BackoffPluginhas been rewritten to be much simpler to build custom retry policies using simple functions rather than various chained classes. See: https://github.com/guzzle/retry-subscriberGuzzle\Http\Plugin\Cache\CachePluginhas moved to https://github.com/guzzle/cache-subscriberGuzzle\Http\Plugin\Log\LogPluginhas moved to https://github.com/guzzle/log-subscriberGuzzle\Http\Plugin\Md5\Md5Pluginhas moved to https://github.com/guzzle/message-integrity-subscriberGuzzle\Http\Plugin\Mock\MockPluginhas moved toGuzzleHttp\Subscriber\MockSubscriber.Guzzle\Http\Plugin\Oauth\OauthPluginhas moved to https://github.com/guzzle/oauth-subscriber
Service
The service description layer of Guzzle has moved into two separate packages:
- https://github.com/guzzle/command Provides a high level abstraction over web services by representing web service operations using commands.
- https://github.com/guzzle/guzzle-services Provides an implementation of guzzle/command that provides request serialization and response parsing using Guzzle service descriptions.
Stream
Stream have moved to a separate package available at https://github.com/guzzle/streams.
Guzzle\Stream\StreamInterface has been given a large update to cleanly take
on the responsibilities of Guzzle\Http\EntityBody and
Guzzle\Http\EntityBodyInterface now that they have been removed. The number
of methods implemented by the StreamInterface has been drastically reduced to
allow developers to more easily extend and decorate stream behavior.
Removed methods from StreamInterface
getStreamandsetStreamhave been removed to better encapsulate streams.getMetadataandsetMetadatahave been removed in favor ofGuzzleHttp\Stream\MetadataStreamInterface.getWrapper,getWrapperData,getStreamType, andgetUrihave all been removed. This data is accessible when using streams that implementGuzzleHttp\Stream\MetadataStreamInterface.rewindhas been removed. Useseek(0)for a similar behavior.
Renamed methods
detachStreamhas been renamed todetach.feofhas been renamed toeof.ftellhas been renamed totell.readLinehas moved from an instance method to a static class method ofGuzzleHttp\Stream\Stream.
Metadata streams
GuzzleHttp\Stream\MetadataStreamInterface has been added to denote streams
that contain additional metadata accessible via getMetadata().
GuzzleHttp\Stream\StreamInterface::getMetadata and
GuzzleHttp\Stream\StreamInterface::setMetadata have been removed.
StreamRequestFactory
The entire concept of the StreamRequestFactory has been removed. The way this
was used in Guzzle 3 broke the actual interface of sending streaming requests
(instead of getting back a Response, you got a StreamInterface). Streaming
PHP requests are now implemented through the GuzzleHttp\Adapter\StreamAdapter.
3.6 to 3.7
Deprecations
- You can now enable E_USER_DEPRECATED warnings to see if you are using any deprecated methods.:
\Guzzle\Common\Version::$emitWarnings = true;
The following APIs and options have been marked as deprecated:
- Marked
Guzzle\Http\Message\Request::isResponseBodyRepeatable()as deprecated. Use$request->getResponseBody()->isRepeatable()instead. - Marked
Guzzle\Http\Message\Request::canCache()as deprecated. UseGuzzle\Plugin\Cache\DefaultCanCacheStrategy->canCacheRequest()instead. - Marked
Guzzle\Http\Message\Request::canCache()as deprecated. UseGuzzle\Plugin\Cache\DefaultCanCacheStrategy->canCacheRequest()instead. - Marked
Guzzle\Http\Message\Request::setIsRedirect()as deprecated. Use the HistoryPlugin instead. - Marked
Guzzle\Http\Message\Request::isRedirect()as deprecated. Use the HistoryPlugin instead. - Marked
Guzzle\Cache\CacheAdapterFactory::factory()as deprecated - Marked
Guzzle\Service\Client::enableMagicMethods()as deprecated. Magic methods can no longer be disabled on a Guzzle\Service\Client. - Marked
Guzzle\Parser\Url\UrlParseras deprecated. Just use PHP'sparse_url()and percent encode your UTF-8. - Marked
Guzzle\Common\Collection::inject()as deprecated. - Marked
Guzzle\Plugin\CurlAuth\CurlAuthPluginas deprecated. Use$client->getConfig()->setPath('request.options/auth', array('user', 'pass', 'Basic|Digest|NTLM|Any'));or$client->setDefaultOption('auth', array('user', 'pass', 'Basic|Digest|NTLM|Any'));
3.7 introduces request.options as a parameter for a client configuration and as an optional argument to all creational
request methods. When paired with a client's configuration settings, these options allow you to specify default settings
for various aspects of a request. Because these options make other previous configuration options redundant, several
configuration options and methods of a client and AbstractCommand have been deprecated.
-
Marked
Guzzle\Service\Client::getDefaultHeaders()as deprecated. Use$client->getDefaultOption('headers'). -
Marked
Guzzle\Service\Client::setDefaultHeaders()as deprecated. Use$client->setDefaultOption('headers/{header_name}', 'value'). -
Marked 'request.params' for
Guzzle\Http\Clientas deprecated. Use$client->setDefaultOption('params/{param_name}', 'value') -
Marked 'command.headers', 'command.response_body' and 'command.on_complete' as deprecated for AbstractCommand. These will work through Guzzle 4.0
$command = $client->getCommand('foo', array( 'command.headers' => array('Test' => '123'), 'command.response_body' => '/path/to/file' )); // Should be changed to: $command = $client->getCommand('foo', array( 'command.request_options' => array( 'headers' => array('Test' => '123'), 'save_as' => '/path/to/file' ) ));
Interface changes
Additions and changes (you will need to update any implementations or subclasses you may have created):
- Added an
$optionsargument to the end of the following methods ofGuzzle\Http\ClientInterface: createRequest, head, delete, put, patch, post, options, prepareRequest - Added an
$optionsargument to the end ofGuzzle\Http\Message\Request\RequestFactoryInterface::createRequest() - Added an
applyOptions()method toGuzzle\Http\Message\Request\RequestFactoryInterface - Changed
Guzzle\Http\ClientInterface::get($uri = null, $headers = null, $body = null)toGuzzle\Http\ClientInterface::get($uri = null, $headers = null, $options = array()). You can still pass in a resource, string, or EntityBody into the $options parameter to specify the download location of the response. - Changed
Guzzle\Common\Collection::__construct($data)to no longer accepts a null value for$databut a defaultarray() - Added
Guzzle\Stream\StreamInterface::isRepeatable - Made
Guzzle\Http\Client::expandTemplateandgetUriTemplateprotected methods.
The following methods were removed from interfaces. All of these methods are still available in the concrete classes that implement them, but you should update your code to use alternative methods:
- Removed
Guzzle\Http\ClientInterface::setDefaultHeaders(). Use$client->getConfig()->setPath('request.options/headers/{header_name}', 'value'). or$client->getConfig()->setPath('request.options/headers', array('header_name' => 'value'))or$client->setDefaultOption('headers/{header_name}', 'value'). or$client->setDefaultOption('headers', array('header_name' => 'value'))`. - Removed
Guzzle\Http\ClientInterface::getDefaultHeaders(). Use$client->getConfig()->getPath('request.options/headers')`. - Removed
Guzzle\Http\ClientInterface::expandTemplate(). This is an implementation detail. - Removed
Guzzle\Http\ClientInterface::setRequestFactory(). This is an implementation detail. - Removed
Guzzle\Http\ClientInterface::getCurlMulti(). This is a very specific implementation detail. - Removed
Guzzle\Http\Message\RequestInterface::canCache. Use the CachePlugin. - Removed
Guzzle\Http\Message\RequestInterface::setIsRedirect. Use the HistoryPlugin. - Removed
Guzzle\Http\Message\RequestInterface::isRedirect. Use the HistoryPlugin.
Cache plugin breaking changes
- CacheKeyProviderInterface and DefaultCacheKeyProvider are no longer used. All of this logic is handled in a CacheStorageInterface. These two objects and interface will be removed in a future version.
- Always setting X-cache headers on cached responses
- Default cache TTLs are now handled by the CacheStorageInterface of a CachePlugin
CacheStorageInterface::cache($key, Response $response, $ttl = null)has changed tocache(RequestInterface $request, Response $response);CacheStorageInterface::fetch($key)has changed tofetch(RequestInterface $request);CacheStorageInterface::delete($key)has changed todelete(RequestInterface $request);- Added
CacheStorageInterface::purge($url) DefaultRevalidation::__construct(CacheKeyProviderInterface $cacheKey, CacheStorageInterface $cache, CachePlugin $plugin)has changed toDefaultRevalidation::__construct(CacheStorageInterface $cache, CanCacheStrategyInterface $canCache = null)- Added
RevalidationInterface::shouldRevalidate(RequestInterface $request, Response $response)
3.5 to 3.6
- Mixed casing of headers are now forced to be a single consistent casing across all values for that header.
- Messages internally use a HeaderCollection object to delegate handling case-insensitive header resolution
- Removed the whole changedHeader() function system of messages because all header changes now go through addHeader(). For example, setHeader() first removes the header using unset on a HeaderCollection and then calls addHeader(). Keeping the Host header and URL host in sync is now handled by overriding the addHeader method in Request.
- Specific header implementations can be created for complex headers. When a message creates a header, it uses a HeaderFactory which can map specific headers to specific header classes. There is now a Link header and CacheControl header implementation.
- Moved getLinks() from Response to just be used on a Link header object.
If you previously relied on Guzzle\Http\Message\Header::raw(), then you will need to update your code to use the HeaderInterface (e.g. toArray(), getAll(), etc.).
Interface changes
- Removed from interface: Guzzle\Http\ClientInterface::setUriTemplate
- Removed from interface: Guzzle\Http\ClientInterface::setCurlMulti()
- Removed Guzzle\Http\Message\Request::receivedRequestHeader() and implemented this functionality in Guzzle\Http\Curl\RequestMediator
- Removed the optional $asString parameter from MessageInterface::getHeader(). Just cast the header to a string.
- Removed the optional $tryChunkedTransfer option from Guzzle\Http\Message\EntityEnclosingRequestInterface
- Removed the $asObjects argument from Guzzle\Http\Message\MessageInterface::getHeaders()
Removed deprecated functions
- Removed Guzzle\Parser\ParserRegister::get(). Use getParser()
- Removed Guzzle\Parser\ParserRegister::set(). Use registerParser().
Deprecations
- The ability to case-insensitively search for header values
- Guzzle\Http\Message\Header::hasExactHeader
- Guzzle\Http\Message\Header::raw. Use getAll()
- Deprecated cache control specific methods on Guzzle\Http\Message\AbstractMessage. Use the CacheControl header object instead.
Other changes
- All response header helper functions return a string rather than mixing Header objects and strings inconsistently
- Removed cURL blacklist support. This is no longer necessary now that Expect, Accept, etc. are managed by Guzzle directly via interfaces
- Removed the injecting of a request object onto a response object. The methods to get and set a request still exist but are a no-op until removed.
- Most classes that used to require a
Guzzle\Service\Command\CommandInterfacetypehint now request aGuzzle\Service\Command\ArrayCommandInterface. - Added
Guzzle\Http\Message\RequestInterface::startResponse()to the RequestInterface to handle injecting a response on a request while the request is still being transferred Guzzle\Service\Command\CommandInterfacenow extends from ToArrayInterface and ArrayAccess
3.3 to 3.4
Base URLs of a client now follow the rules of https://datatracker.ietf.org/doc/html/rfc3986#section-5.2.2 when merging URLs.
3.2 to 3.3
Response::getEtag() quote stripping removed
Guzzle\Http\Message\Response::getEtag() no longer strips quotes around the ETag response header
Removed Guzzle\Http\Utils
The Guzzle\Http\Utils class was removed. This class was only used for testing.
Stream wrapper and type
Guzzle\Stream\Stream::getWrapper() and Guzzle\Stream\Stream::getStreamType() are no longer converted to lowercase.
curl.emit_io became emit_io
Emitting IO events from a RequestMediator is now a parameter that must be set in a request's curl options using the 'emit_io' key. This was previously set under a request's parameters using 'curl.emit_io'
3.1 to 3.2
CurlMulti is no longer reused globally
Before 3.2, the same CurlMulti object was reused globally for each client. This can cause issue where plugins added to a single client can pollute requests dispatched from other clients.
If you still wish to reuse the same CurlMulti object with each client, then you can add a listener to the
ServiceBuilder's service_builder.create_client event to inject a custom CurlMulti object into each client as it is
created.
$multi = new Guzzle\Http\Curl\CurlMulti();
$builder = Guzzle\Service\Builder\ServiceBuilder::factory('/path/to/config.json');
$builder->addListener('service_builder.create_client', function ($event) use ($multi) {
$event['client']->setCurlMulti($multi);
}
});
No default path
URLs no longer have a default path value of '/' if no path was specified.
Before:
$request = $client->get('http://www.foo.com');
echo $request->getUrl();
// >> http://www.foo.com/
After:
$request = $client->get('http://www.foo.com');
echo $request->getUrl();
// >> http://www.foo.com
Less verbose BadResponseException
The exception message for Guzzle\Http\Exception\BadResponseException no longer contains the full HTTP request and
response information. You can, however, get access to the request and response object by calling getRequest() or
getResponse() on the exception object.
Query parameter aggregation
Multi-valued query parameters are no longer aggregated using a callback function. Guzzle\Http\Query now has a
setAggregator() method that accepts a Guzzle\Http\QueryAggregator\QueryAggregatorInterface object. This object is
responsible for handling the aggregation of multi-valued query string variables into a flattened hash.
2.8 to 3.x
Guzzle\Service\Inspector
Change \Guzzle\Service\Inspector::fromConfig to \Guzzle\Common\Collection::fromConfig
Before
use Guzzle\Service\Inspector;
class YourClient extends \Guzzle\Service\Client
{
public static function factory($config = array())
{
$default = array();
$required = array('base_url', 'username', 'api_key');
$config = Inspector::fromConfig($config, $default, $required);
$client = new self(
$config->get('base_url'),
$config->get('username'),
$config->get('api_key')
);
$client->setConfig($config);
$client->setDescription(ServiceDescription::factory(__DIR__ . DIRECTORY_SEPARATOR . 'client.json'));
return $client;
}
After
use Guzzle\Common\Collection;
class YourClient extends \Guzzle\Service\Client
{
public static function factory($config = array())
{
$default = array();
$required = array('base_url', 'username', 'api_key');
$config = Collection::fromConfig($config, $default, $required);
$client = new self(
$config->get('base_url'),
$config->get('username'),
$config->get('api_key')
);
$client->setConfig($config);
$client->setDescription(ServiceDescription::factory(__DIR__ . DIRECTORY_SEPARATOR . 'client.json'));
return $client;
}
Convert XML Service Descriptions to JSON
Before
<?xml version="1.0" encoding="UTF-8"?>
<client>
<commands>
<!-- Groups -->
<command name="list_groups" method="GET" uri="groups.json">
<doc>Get a list of groups</doc>
</command>
<command name="search_groups" method="GET" uri='search.json?query="{{query}} type:group"'>
<doc>Uses a search query to get a list of groups</doc>
<param name="query" type="string" required="true" />
</command>
<command name="create_group" method="POST" uri="groups.json">
<doc>Create a group</doc>
<param name="data" type="array" location="body" filters="json_encode" doc="Group JSON"/>
<param name="Content-Type" location="header" static="application/json"/>
</command>
<command name="delete_group" method="DELETE" uri="groups/{{id}}.json">
<doc>Delete a group by ID</doc>
<param name="id" type="integer" required="true"/>
</command>
<command name="get_group" method="GET" uri="groups/{{id}}.json">
<param name="id" type="integer" required="true"/>
</command>
<command name="update_group" method="PUT" uri="groups/{{id}}.json">
<doc>Update a group</doc>
<param name="id" type="integer" required="true"/>
<param name="data" type="array" location="body" filters="json_encode" doc="Group JSON"/>
<param name="Content-Type" location="header" static="application/json"/>
</command>
</commands>
</client>
After
{
"name": "Zendesk REST API v2",
"apiVersion": "2012-12-31",
"description":"Provides access to Zendesk views, groups, tickets, ticket fields, and users",
"operations": {
"list_groups": {
"httpMethod":"GET",
"uri": "groups.json",
"summary": "Get a list of groups"
},
"search_groups":{
"httpMethod":"GET",
"uri": "search.json?query=\"{query} type:group\"",
"summary": "Uses a search query to get a list of groups",
"parameters":{
"query":{
"location": "uri",
"description":"Zendesk Search Query",
"type": "string",
"required": true
}
}
},
"create_group": {
"httpMethod":"POST",
"uri": "groups.json",
"summary": "Create a group",
"parameters":{
"data": {
"type": "array",
"location": "body",
"description":"Group JSON",
"filters": "json_encode",
"required": true
},
"Content-Type":{
"type": "string",
"location":"header",
"static": "application/json"
}
}
},
"delete_group": {
"httpMethod":"DELETE",
"uri": "groups/{id}.json",
"summary": "Delete a group",
"parameters":{
"id":{
"location": "uri",
"description":"Group to delete by ID",
"type": "integer",
"required": true
}
}
},
"get_group": {
"httpMethod":"GET",
"uri": "groups/{id}.json",
"summary": "Get a ticket",
"parameters":{
"id":{
"location": "uri",
"description":"Group to get by ID",
"type": "integer",
"required": true
}
}
},
"update_group": {
"httpMethod":"PUT",
"uri": "groups/{id}.json",
"summary": "Update a group",
"parameters":{
"id": {
"location": "uri",
"description":"Group to update by ID",
"type": "integer",
"required": true
},
"data": {
"type": "array",
"location": "body",
"description":"Group JSON",
"filters": "json_encode",
"required": true
},
"Content-Type":{
"type": "string",
"location":"header",
"static": "application/json"
}
}
}
}
Guzzle\Service\Description\ServiceDescription
Commands are now called Operations
Before
use Guzzle\Service\Description\ServiceDescription;
$sd = new ServiceDescription();
$sd->getCommands(); // @returns ApiCommandInterface[]
$sd->hasCommand($name);
$sd->getCommand($name); // @returns ApiCommandInterface|null
$sd->addCommand($command); // @param ApiCommandInterface $command
After
use Guzzle\Service\Description\ServiceDescription;
$sd = new ServiceDescription();
$sd->getOperations(); // @returns OperationInterface[]
$sd->hasOperation($name);
$sd->getOperation($name); // @returns OperationInterface|null
$sd->addOperation($operation); // @param OperationInterface $operation
Guzzle\Common\Inflection\Inflector
Namespace is now Guzzle\Inflection\Inflector
Guzzle\Http\Plugin
Namespace is now Guzzle\Plugin. Many other changes occur within this namespace and are detailed in their own sections below.
Guzzle\Http\Plugin\LogPlugin and Guzzle\Common\Log
Now Guzzle\Plugin\Log\LogPlugin and Guzzle\Log respectively.
Before
use Guzzle\Common\Log\ClosureLogAdapter;
use Guzzle\Http\Plugin\LogPlugin;
/** @var \Guzzle\Http\Client */
$client;
// $verbosity is an integer indicating desired message verbosity level
$client->addSubscriber(new LogPlugin(new ClosureLogAdapter(function($m) { echo $m; }, $verbosity = LogPlugin::LOG_VERBOSE);
After
use Guzzle\Log\ClosureLogAdapter;
use Guzzle\Log\MessageFormatter;
use Guzzle\Plugin\Log\LogPlugin;
/** @var \Guzzle\Http\Client */
$client;
// $format is a string indicating desired message format -- @see MessageFormatter
$client->addSubscriber(new LogPlugin(new ClosureLogAdapter(function($m) { echo $m; }, $format = MessageFormatter::DEBUG_FORMAT);
Guzzle\Http\Plugin\CurlAuthPlugin
Now Guzzle\Plugin\CurlAuth\CurlAuthPlugin.
Guzzle\Http\Plugin\ExponentialBackoffPlugin
Now Guzzle\Plugin\Backoff\BackoffPlugin, and other changes.
Before
use Guzzle\Http\Plugin\ExponentialBackoffPlugin;
$backoffPlugin = new ExponentialBackoffPlugin($maxRetries, array_merge(
ExponentialBackoffPlugin::getDefaultFailureCodes(), array(429)
));
$client->addSubscriber($backoffPlugin);
After
use Guzzle\Plugin\Backoff\BackoffPlugin;
use Guzzle\Plugin\Backoff\HttpBackoffStrategy;
// Use convenient factory method instead -- see implementation for ideas of what
// you can do with chaining backoff strategies
$backoffPlugin = BackoffPlugin::getExponentialBackoff($maxRetries, array_merge(
HttpBackoffStrategy::getDefaultFailureCodes(), array(429)
));
$client->addSubscriber($backoffPlugin);
Known Issues
[BUG] Accept-Encoding header behavior changed unintentionally.
(See #217) (Fixed in 09daeb8c666fb44499a0646d655a8ae36456575e)
In version 2.8 setting the Accept-Encoding header would set the CURLOPT_ENCODING option, which permitted cURL to
properly handle gzip/deflate compressed responses from the server. In versions affected by this bug this does not happen.
See issue #217 for a workaround, or use a version containing the fix.