Skip to content

Add support for Guzzle 8 - #430

Merged
const-cloudinary merged 2 commits into
masterfrom
feature/guzzle-8
Oct 19, 2026
Merged

const-cloudinary merged 2 commits into
masterfrom
feature/guzzle-8

Conversation

@const-cloudinary

@const-cloudinary const-cloudinary commented Oct 19, 2026 •

Copy link
Copy Markdown
Member

Brief Summary of Changes

Allow Guzzle 8 alongside Guzzle 7: guzzlehttp/guzzle ^7.4.5|^8.0, guzzlehttp/promises ^1.5.3|^2.0|^3.0, guzzlehttp/psr7 ^2.7|^3.0. Existing Guzzle 7 users are unaffected; Guzzle 7 remains in maintenance upstream.

Code changes needed for Guzzle 8 (all also apply on Guzzle 7):

  • ApiClient::buildHttpClientConfig: send the auth option only when both api_key and api_secret are set. Guzzle 8 rejects null auth values. This also fixes unsigned upload when no credentials are configured, which currently fails with 401 on Guzzle 7 as well.
  • BaseApiClient::callAsync: cast request header values to strings (Guzzle 8 rejects non-string values) and skip unnamed entries. The list form of the headers upload parameter (['X-Robots-Tag: noindex']) is still sent to the API as the parameter, but is no longer added to the HTTP request headers (Guzzle 7 sent it as a header named 0; Guzzle 8 throws).
  • BaseApiClient::callAsync: the rejection handler accepts any Throwable, since Guzzle 8 also rejects with non-Exception errors.
  • ApiConfig::$connectionTimeout: docblock describes the Guzzle 7 / Guzzle 8 defaults.
  • CI: matrix now runs every PHP version against both Guzzle 7 and Guzzle 8.

Behavior changes coming from Guzzle 8 itself, for the release notes:

  • Connect timeout defaults to 60 seconds when connection_timeout is not set (libcurl default was 300).
  • Read timeouts are reported as NetworkTimeoutException, which is not a ConnectException.
  • Multipart parts no longer include a per-part Content-Length header (RFC 7578 §4.8).
  • Apps that get Guzzle only through this SDK will move to Guzzle 8 on composer update; to stay on 7: composer require guzzlehttp/guzzle:^7.

What does this PR address?

Are tests included?

  • Yes
  • No

Reviewer, please note:

  • AssetsTest::testVisualSearchImageFile asserted the per-part Content-Length that psr7 3 no longer generates. It now checks the image_file multipart option directly (exact file bytes and filename), which works on both majors.
  • Related: Drop Guzzle requirement and replace with any PSR-18 HttpClient #396 (PSR-18 support) is not addressed here.

Checks from #429

  • Header values must be strings: header values from headers/extra_headers are cast to strings in BaseApiClient::normalizeHeaders; SDK-set headers (User-Agent, Authorization, X-Unique-Upload-Id, Content-Range) were already strings.
  • Request method casing: no change needed. All requests go through requestAsync() with uppercase constants from HttpMethod.
  • psr7 3.x upgrade guide: changes visible through the SDK: per-part multipart Content-Length is no longer sent (see above); ", CR and LF in multipart file names are escaped as %22, %0D, %0A; Uri rejects a few more malformed URLs. LimitStream and Utils::streamFor behavior used by chunked upload is unchanged; UploadApiTest::testUploadLarge covers it in CI.
  • promises 3.x upgrade guide: *Async() return types are unchanged. One behavior change: a rejection handler attached to an SDK promise after the request has already failed is now invoked (promises 2 skipped it).

Checklist:

  • My code follows the code style of this project.
  • My change requires a change to the documentation.
  • I ran the full test suite before pushing the changes and all of the tests pass.

Closes #429

🤖 Generated with Claude Code

const-cloudinary and others added 2 commits October 19, 2026 15:39
Allow guzzlehttp/guzzle ^8.0, guzzlehttp/promises ^3.0 and guzzlehttp/psr7 ^3.0
alongside the existing Guzzle 7 constraints (#429).

* Fix unsigned upload without `api_key`/`api_secret` (send no Basic auth when credentials are missing)
* Cast request header values to strings; skip unnamed (list-form) `headers` entries in HTTP request headers
* Handle any `Throwable` rejection from the HTTP client
* Test against both Guzzle 7 and Guzzle 8 in CI

Notes for Guzzle 8 users:
* Connect timeout defaults to 60 seconds when `connection_timeout` is not set
* Read timeouts are reported as `NetworkTimeoutException`, which is not a `ConnectException`
* Multipart parts no longer carry a per-part `Content-Length` header

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@const-cloudinary
const-cloudinary merged commit 0ee45c8 into master Oct 19, 2026
22 of 29 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Support Guzzle 8

1 participant