2771 lines
129 KiB
Markdown
2771 lines
129 KiB
Markdown
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][psr7-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][psr7-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][promises-upgrade-guide] if you
|
|
use async requests, custom handlers, middleware, pools, or promise helpers.
|
|
|
|
Guzzle 8 now requires `psr/http-factory:^1.0` directly.
|
|
|
|
[psr7-upgrade-guide]: https://github.com/guzzle/psr7/blob/3.0/UPGRADING.md
|
|
[promises-upgrade-guide]: https://github.com/guzzle/promises/blob/3.0/UPGRADING.md
|
|
|
|
#### 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][psr7-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:
|
|
|
|
```php
|
|
// 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:
|
|
|
|
```php
|
|
$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:
|
|
|
|
```php
|
|
$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\ResponseException` with that response attached. This
|
|
exception pre-empts the `ClientException` or `ServerException` that
|
|
`http_errors` would 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 the `auth` option.
|
|
- **Probe payload headers.** The probe strips the payload-describing headers:
|
|
`Expect`, `Transfer-Encoding`, `Trailer`, `Content-Range`,
|
|
`Content-Encoding`, `Content-MD5`, `Digest`, `Content-Digest`, and
|
|
`Repr-Digest`, and forces `Content-Length: 0`. `Content-Type` is kept, as
|
|
libcurl does. libcurl's probe is bodyless but not header-identical: it keeps a
|
|
caller-supplied `Expect`, keeps HTTP/1.1 `Transfer-Encoding`, sending a
|
|
chunked probe with a zero chunk and no `Content-Length`, and forwards other
|
|
caller headers untouched. Guzzle's stripping is deliberately stricter.
|
|
- **`auth-int` is rejected.** Challenges offering only `qop=auth-int` are
|
|
ignored. libcurl selects `auth-int` when `auth` is 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-Authenticate` header 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-Authenticate` header 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, `stale` and `userhash` flags 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
|
|
`domain` parameter when scoping reuse, and curl's Digest state has no `domain`
|
|
handling. Guzzle generates a fresh cnonce per Authorization header, where
|
|
curl's non-SSPI implementation reuses one cnonce per nonce while incrementing
|
|
`nc`. When `domain` is 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-side `domain`, or disable reuse by replacing the
|
|
default auth middleware with `GuzzleHttp\Middleware::auth(false)` as shown in
|
|
the `auth` request option documentation.
|
|
- **Per-leg observability.** Each handshake leg is a separate transfer:
|
|
`on_stats`, `on_headers`, and `progress` fire per leg, and `delay` applies
|
|
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 => true` plus a configured `sink`, a Digest
|
|
request drains the final body into the sink, protecting sinks from challenge
|
|
bodies on handlers that ignore `stream`. 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`.
|
|
|
|
```php
|
|
// 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:
|
|
|
|
```text
|
|
. \RuntimeException
|
|
└── TransferException (implements GuzzleException)
|
|
├── ConnectException (implements NetworkExceptionInterface)
|
|
└── RequestException (implements RequestExceptionInterface)
|
|
├── BadResponseException
|
|
│ ├── ClientException
|
|
│ └── ServerException
|
|
└── TooManyRedirectsException
|
|
```
|
|
|
|
Guzzle 8.0 uses this hierarchy:
|
|
|
|
```text
|
|
. \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`:
|
|
|
|
```php
|
|
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 are `ResponseTimeoutException`, while
|
|
`sink` write failures, progress callback failures, deterministic response
|
|
size/platform-limit failures, or request-body stalls after headers are plain
|
|
`ResponseException` instances.
|
|
- Post-transfer response finalization failures are also plain
|
|
`ResponseException`. A seekable response sink that fails to rewind does not
|
|
become a `ResponseTransferException`. 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:
|
|
|
|
```php
|
|
// 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:
|
|
|
|
```php
|
|
$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`.
|
|
|
|
```php
|
|
// 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:
|
|
|
|
```php
|
|
$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`.
|
|
|
|
```php
|
|
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:
|
|
|
|
```php
|
|
// 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:
|
|
|
|
```php
|
|
// 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\UriTemplate` is removed.
|
|
- Class `GuzzleHttp\Exception\SeekException` is removed.
|
|
- Classes `GuzzleHttp\Exception\BadResponseException`, `GuzzleHttp\Exception\ClientException`,
|
|
`GuzzleHttp\Exception\ServerException` can no longer be initialized with an empty
|
|
Response as argument.
|
|
- Class `GuzzleHttp\Exception\ConnectException` now extends `GuzzleHttp\Exception\TransferException`
|
|
instead of `GuzzleHttp\Exception\RequestException`.
|
|
- Function `GuzzleHttp\Exception\ConnectException::getResponse()` is removed.
|
|
- Function `GuzzleHttp\Exception\ConnectException::hasResponse()` is removed.
|
|
- Constant `GuzzleHttp\ClientInterface::VERSION` is removed. Added `GuzzleHttp\ClientInterface::MAJOR_VERSION` instead.
|
|
- Function `GuzzleHttp\Exception\RequestException::getResponseBodySummary` is removed.
|
|
Use `\GuzzleHttp\Psr7\get_message_body_summary` as an alternative.
|
|
- Function `GuzzleHttp\Cookie\CookieJar::getCookieValue` is removed.
|
|
- Request option `exceptions` is removed. Please use `http_errors`.
|
|
- Request option `save_to` is removed. Please use `sink`.
|
|
- Pool option `pool_size` is removed. Please use `concurrency`.
|
|
- We now look for environment variables in the `$_SERVER` super global, due to thread safety issues with `getenv`. We continue to fallback to `getenv` in CLI environments, for maximum compatibility.
|
|
- The `get`, `head`, `put`, `post`, `patch`, `delete`, `getAsync`, `headAsync`, `putAsync`, `postAsync`, `patchAsync`, and `deleteAsync` methods are now implemented as genuine methods on `GuzzleHttp\Client`, with strong typing. The original `__call` implementation remains unchanged for now, for maximum backwards compatibility, but won't be invoked under normal operation.
|
|
- The `log` middleware will log the errors with level `error` instead of `notice`
|
|
- 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:
|
|
|
|
```php
|
|
// Before:
|
|
curl_version();
|
|
|
|
// After:
|
|
\curl_version();
|
|
```
|
|
|
|
For the full diff you can check [here](https://github.com/guzzle/guzzle/compare/6.5.4..7.0.0).
|
|
|
|
5.0 to 6.0
|
|
----------
|
|
|
|
Guzzle now uses [PSR-7](https://www.php-fig.org/psr/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/streams` dependency has been removed. Stream functionality
|
|
is now present in the `GuzzleHttp\Psr7` namespace provided by the
|
|
`guzzlehttp/psr7` package.
|
|
- Guzzle no longer uses ReactPHP promises and now uses the
|
|
`guzzlehttp/promises` library. We use a custom promise library for three
|
|
significant reasons:
|
|
1. 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.
|
|
2. 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).
|
|
3. 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\Mimetypes` has been moved to a function in
|
|
`GuzzleHttp\Psr7\mimetype_from_extension` and
|
|
`GuzzleHttp\Psr7\mimetype_from_filename`.
|
|
- `GuzzleHttp\Query` and `GuzzleHttp\QueryParser` have been removed. Query
|
|
strings must now be passed into request objects as strings, or provided to
|
|
the `query` request option when creating requests with clients. The `query`
|
|
option uses PHP's `http_build_query` to 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_query` and
|
|
`GuzzleHttp\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\Handler` namespace. 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 `Event` namespace.
|
|
- Removed the `Subscriber` namespace.
|
|
- Removed `Transaction` class
|
|
- Removed `RequestFsm`
|
|
- Removed `RingBridge`
|
|
- `GuzzleHttp\Subscriber\Cookie` is now provided by
|
|
`GuzzleHttp\Middleware::cookies`
|
|
- `GuzzleHttp\Subscriber\HttpError` is now provided by
|
|
`GuzzleHttp\Middleware::httpError`
|
|
- `GuzzleHttp\Subscriber\History` is now provided by
|
|
`GuzzleHttp\Middleware::history`
|
|
- `GuzzleHttp\Subscriber\Mock` is now provided by
|
|
`GuzzleHttp\Handler\MockHandler`
|
|
- `GuzzleHttp\Subscriber\Prepare` is now provided by
|
|
`GuzzleHttp\PrepareBodyMiddleware`
|
|
- `GuzzleHttp\Subscriber\Redirect` is now provided by
|
|
`GuzzleHttp\RedirectMiddleware`
|
|
- Guzzle now uses `Psr\Http\Message\UriInterface` (implements in
|
|
`GuzzleHttp\Psr7\Uri`) for URI support. `GuzzleHttp\Url` is now gone.
|
|
- Static functions in `GuzzleHttp\Utils` have been moved to namespaced
|
|
functions under the `GuzzleHttp` namespace. This requires either a Composer
|
|
based autoloader or you to include functions.php.
|
|
- `GuzzleHttp\ClientInterface::getDefaultOption` has been renamed to
|
|
`GuzzleHttp\ClientInterface::getConfig`.
|
|
- `GuzzleHttp\ClientInterface::setDefaultOption` has been removed.
|
|
- The `json` and `xml` methods 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:
|
|
|
|
```php
|
|
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.
|
|
|
|
```php
|
|
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`](https://github.com/guzzle/guzzle/blob/6.5/docs/request-options.rst#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`](https://github.com/guzzle/guzzle/blob/6.5/docs/request-options.rst#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](https://github.com/guzzle/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](https://ocramius.github.io/blog/fluent-interfaces-are-evil/)
|
|
from the following classes:
|
|
|
|
- `GuzzleHttp\Collection`
|
|
- `GuzzleHttp\Url`
|
|
- `GuzzleHttp\Query`
|
|
- `GuzzleHttp\Post\PostBody`
|
|
- `GuzzleHttp\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's `json_decode`
|
|
- `GuzzleHttp\get_path` -> `GuzzleHttp\Utils::getPath`
|
|
- `GuzzleHttp\Utils::setPath` -> `GuzzleHttp\set_path`
|
|
- `GuzzleHttp\Pool::batch` -> `GuzzleHttp\batch`. This function is, however,
|
|
deprecated in favor of using `GuzzleHttp\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.
|
|
- `FromConfigInterface` has been removed.
|
|
- `Guzzle\Common\Version` has been removed. The VERSION constant can be found
|
|
at `GuzzleHttp\ClientInterface::VERSION`.
|
|
|
|
### Collection
|
|
|
|
- `getAll` has been removed. Use `toArray` to convert a collection to an array.
|
|
- `inject` has been removed.
|
|
- `keySearch` has been removed.
|
|
- `getPath` no longer supports wildcard expressions. Use something better like
|
|
JMESPath for this.
|
|
- `setPath` now 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\EventDispatcherInterface` is replaced by
|
|
`GuzzleHttp\Event\EmitterInterface`.
|
|
- `Symfony\Component\EventDispatcher\EventDispatcher` is replaced by
|
|
`GuzzleHttp\Event\Emitter`.
|
|
- `Symfony\Component\EventDispatcher\Event` is replaced by
|
|
`GuzzleHttp\Event\Event`, and Guzzle now has an EventInterface in
|
|
`GuzzleHttp\Event\EventInterface`.
|
|
- `AbstractHasDispatcher` has moved to a trait, `HasEmitterTrait`, and
|
|
`HasDispatcherInterface` has moved to `HasEmitterInterface`. Retrieving the
|
|
event emitter of a request, client, etc. now uses the `getEmitter` method
|
|
rather than the `getDispatcher` method.
|
|
|
|
#### 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
|
|
the `getListeners()` method.
|
|
- Use `emit()` instead of `dispatch()` to emit an event from an emitter.
|
|
- Use `attach()` instead of `addSubscriber()` and `detach()` instead of
|
|
`removeSubscriber()`.
|
|
|
|
```php
|
|
$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.
|
|
|
|
```php
|
|
// 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 `complete` and `error`
|
|
events to asynchronously manage parallel request transfers.
|
|
- `Guzzle\Http\Url` has moved to `GuzzleHttp\Url`.
|
|
- `Guzzle\Http\QueryString` has moved to `GuzzleHttp\Query`.
|
|
- QueryAggregators have been rewritten so that they are simply callable
|
|
functions.
|
|
- `GuzzleHttp\StaticClient` has been removed. Use the functions provided in
|
|
`functions.php` for an easy to use static client instance.
|
|
- Exceptions in `GuzzleHttp\Exception` have been updated to all extend from
|
|
`GuzzleHttp\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.
|
|
|
|
```php
|
|
// 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 `send` method no longer accepts more than one request. Use `sendAll` to
|
|
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_url` string or array to use a URI template as the base URL of a client.
|
|
You can also specify a `defaults` key that is an associative array of default
|
|
request options. You can pass an `adapter` to use a custom adapter,
|
|
`batch_adapter` to use a custom adapter for sending requests in parallel, or
|
|
a `message_factory` to change the factory used to create HTTP requests and
|
|
responses.
|
|
- The client no longer emits a `client.create_request` event.
|
|
- 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\EntityEnclosingRequest` and
|
|
`GuzzleHttp\Message\EntityEnclosingRequestInterface` have been removed. The
|
|
separation between requests that contain a body and requests that do not
|
|
contain a body has been removed, and now `GuzzleHttp\Message\RequestInterface`
|
|
handles both use cases.
|
|
- Any method that previously accepts a `GuzzleHttp\Response` object now accept a
|
|
`GuzzleHttp\Message\ResponseInterface`.
|
|
- `GuzzleHttp\Message\RequestFactoryInterface` has been renamed to
|
|
`GuzzleHttp\Message\MessageFactoryInterface`. This interface is used to create
|
|
both requests and responses and is implemented in
|
|
`GuzzleHttp\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\PostBodyInterface`
|
|
to control the format of a POST body. Requests that are created using a
|
|
standard `GuzzleHttp\Message\MessageFactoryInterface` will automatically use
|
|
a `GuzzleHttp\Post\PostBody` body if the body was passed as an array or if
|
|
the method is POST and no body is provided.
|
|
|
|
```php
|
|
$request = $client->createRequest('POST', '/');
|
|
$request->getBody()->setField('foo', 'bar');
|
|
$request->getBody()->addFile(new PostFile('file_key', fopen('/path/to/content', 'r')));
|
|
```
|
|
|
|
#### Headers
|
|
|
|
- `GuzzleHttp\Message\Header` has 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 of `true` to retrieve a header value as an array
|
|
of strings instead of a single concatenated string.
|
|
- `GuzzleHttp\PostFile` and `GuzzleHttp\PostFileInterface` have been moved to
|
|
`GuzzleHttp\Post`. This interface has been simplified and now allows the
|
|
addition of arbitrary headers.
|
|
- Custom headers like `GuzzleHttp\Message\Header\Link` have been removed. Most
|
|
of the custom headers are now handled separately in specific
|
|
subscribers/plugins, and `GuzzleHttp\Message\HeaderValues::parseParams()` has
|
|
been updated to properly handle headers that contain parameters (like the
|
|
`Link` header).
|
|
|
|
#### Responses
|
|
|
|
- `GuzzleHttp\Message\Response::getInfo()` and
|
|
`GuzzleHttp\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 use `getHeader('Content-MD5')` instead.
|
|
- `GuzzleHttp\Message\Response::setRequest()` and
|
|
`GuzzleHttp\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. Use `getStatusCode()` 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.
|
|
|
|
```php
|
|
// 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.
|
|
|
|
```php
|
|
// 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\EntityBodyInterface` is now `GuzzleHttp\Stream\StreamInterface`
|
|
- `Guzzle\Http\EntityBody` is now `GuzzleHttp\Stream\Stream`
|
|
- `Guzzle\Http\CachingEntityBody` is now `GuzzleHttp\Stream\CachingStream`
|
|
- `Guzzle\Http\ReadLimitEntityBody` is now `GuzzleHttp\Stream\LimitStream`
|
|
- `Guzzle\Http\IoEmittyinEntityBody` has 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_send` has been renamed to `before` and now emits a
|
|
`GuzzleHttp\Event\BeforeEvent`
|
|
- `request.complete` has been renamed to `complete` and now emits a
|
|
`GuzzleHttp\Event\CompleteEvent`.
|
|
- `request.sent` has been removed. Use `complete`.
|
|
- `request.success` has been removed. Use `complete`.
|
|
- `error` is now an event that emits a `GuzzleHttp\Event\ErrorEvent`.
|
|
- `request.exception` has been removed. Use `error`.
|
|
- `request.receive.status_line` has been removed.
|
|
- `curl.callback.progress` has been removed. Use a custom `StreamInterface` to
|
|
maintain a status update.
|
|
- `curl.callback.write` has been removed. Use a custom `StreamInterface` to
|
|
intercept writes.
|
|
- `curl.callback.read` has been removed. Use a custom `StreamInterface` to
|
|
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`, and
|
|
`Guzzle\Iterator\MethodProxyIterator` are nice, but not a core requirement of
|
|
Guzzle itself.
|
|
- `Guzzle\Iterator\FilterIterator` is no longer needed because an equivalent
|
|
class is shipped with PHP 5.4.
|
|
- `Guzzle\Iterator\MapIterator` is 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 was `Guzzle\Http\Url::factory()`). If custom URL parsing is necessary,
|
|
then developers are free to subclass `GuzzleHttp\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 to `GuzzleHttp\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\Async` has been removed.
|
|
- `GuzzleHttp\Plugin\CurlAuth` has been removed.
|
|
- `GuzzleHttp\Plugin\ErrorResponse\ErrorResponsePlugin` has 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\BackoffPlugin` has 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-subscriber
|
|
- `Guzzle\Http\Plugin\Cache\CachePlugin` has moved to
|
|
https://github.com/guzzle/cache-subscriber
|
|
- `Guzzle\Http\Plugin\Log\LogPlugin` has moved to
|
|
https://github.com/guzzle/log-subscriber
|
|
- `Guzzle\Http\Plugin\Md5\Md5Plugin` has moved to
|
|
https://github.com/guzzle/message-integrity-subscriber
|
|
- `Guzzle\Http\Plugin\Mock\MockPlugin` has moved to
|
|
`GuzzleHttp\Subscriber\MockSubscriber`.
|
|
- `Guzzle\Http\Plugin\Oauth\OauthPlugin` has 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
|
|
|
|
- `getStream` and `setStream` have been removed to better encapsulate streams.
|
|
- `getMetadata` and `setMetadata` have been removed in favor of
|
|
`GuzzleHttp\Stream\MetadataStreamInterface`.
|
|
- `getWrapper`, `getWrapperData`, `getStreamType`, and `getUri` have all been
|
|
removed. This data is accessible when
|
|
using streams that implement `GuzzleHttp\Stream\MetadataStreamInterface`.
|
|
- `rewind` has been removed. Use `seek(0)` for a similar behavior.
|
|
|
|
## Renamed methods
|
|
|
|
- `detachStream` has been renamed to `detach`.
|
|
- `feof` has been renamed to `eof`.
|
|
- `ftell` has been renamed to `tell`.
|
|
- `readLine` has moved from an instance method to a static class method of
|
|
`GuzzleHttp\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.:
|
|
|
|
```php
|
|
\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. Use `Guzzle\Plugin\Cache\DefaultCanCacheStrategy->canCacheRequest()` instead.
|
|
- Marked `Guzzle\Http\Message\Request::canCache()` as deprecated. Use `Guzzle\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\UrlParser` as deprecated. Just use PHP's `parse_url()` and percent encode your UTF-8.
|
|
- Marked `Guzzle\Common\Collection::inject()` as deprecated.
|
|
- Marked `Guzzle\Plugin\CurlAuth\CurlAuthPlugin` as 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\Client` as 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 `$options` argument to the end of the following methods of `Guzzle\Http\ClientInterface`:
|
|
createRequest, head, delete, put, patch, post, options, prepareRequest
|
|
- Added an `$options` argument to the end of `Guzzle\Http\Message\Request\RequestFactoryInterface::createRequest()`
|
|
- Added an `applyOptions()` method to `Guzzle\Http\Message\Request\RequestFactoryInterface`
|
|
- Changed `Guzzle\Http\ClientInterface::get($uri = null, $headers = null, $body = null)` to
|
|
`Guzzle\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 `$data` but a
|
|
default `array()`
|
|
- Added `Guzzle\Stream\StreamInterface::isRepeatable`
|
|
- Made `Guzzle\Http\Client::expandTemplate` and `getUriTemplate` protected 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 to `cache(RequestInterface
|
|
$request, Response $response);`
|
|
- `CacheStorageInterface::fetch($key)` has changed to `fetch(RequestInterface $request);`
|
|
- `CacheStorageInterface::delete($key)` has changed to `delete(RequestInterface $request);`
|
|
- Added `CacheStorageInterface::purge($url)`
|
|
- `DefaultRevalidation::__construct(CacheKeyProviderInterface $cacheKey, CacheStorageInterface $cache, CachePlugin
|
|
$plugin)` has changed to `DefaultRevalidation::__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\CommandInterface` typehint now request a
|
|
`Guzzle\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\CommandInterface` now 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.
|
|
|
|
```php
|
|
$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:
|
|
|
|
```php
|
|
$request = $client->get('http://www.foo.com');
|
|
echo $request->getUrl();
|
|
// >> http://www.foo.com/
|
|
```
|
|
|
|
After:
|
|
|
|
```php
|
|
$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**
|
|
|
|
```php
|
|
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**
|
|
|
|
```php
|
|
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
|
|
<?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**
|
|
|
|
```json
|
|
{
|
|
"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**
|
|
|
|
```php
|
|
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**
|
|
|
|
```php
|
|
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**
|
|
|
|
```php
|
|
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**
|
|
|
|
```php
|
|
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**
|
|
|
|
```php
|
|
use Guzzle\Http\Plugin\ExponentialBackoffPlugin;
|
|
|
|
$backoffPlugin = new ExponentialBackoffPlugin($maxRetries, array_merge(
|
|
ExponentialBackoffPlugin::getDefaultFailureCodes(), array(429)
|
|
));
|
|
|
|
$client->addSubscriber($backoffPlugin);
|
|
```
|
|
|
|
**After**
|
|
|
|
```php
|
|
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.
|