Skip to content

Add support for custom PSR-18 HTTP clients - #431

Merged
const-cloudinary merged 1 commit into
masterfrom
feature/psr18-http-client
Oct 19, 2026
Merged

const-cloudinary merged 1 commit into
masterfrom
feature/psr18-http-client

Conversation

@const-cloudinary

Copy link
Copy Markdown
Member

Brief Summary of Changes

Lets applications send SDK requests through their own PSR-18 HTTP client, so their logging, retries, proxy, and timeouts apply. Without it, the SDK keeps using its built-in Guzzle client exactly as before.

$cloudinary = new Cloudinary();
$cloudinary->setHttpClient(new Psr18Client(HttpClient::create(), new HttpFactory(), new HttpFactory()));
  • Cloudinary::setHttpClient() sets the client for API objects returned by uploadApi(), adminApi(), searchApi(), and searchFoldersApi().
  • setHttpClient() on UploadApi, AdminApi, SearchApi, SearchFoldersApi, and AccountApi sets it for objects created directly.
  • Cloudinary::setDefaultHttpClient() sets a process-wide default. An object's own client wins over the default, which wins over the built-in client.
  • Psr18Handler is a Guzzle handler that forwards each built request to the PSR-18 client and maps PSR-18 network and client errors to ConnectException and RequestException. The public API, including *Async() return types, is unchanged.
  • psr/http-client is now a direct requirement (already installed through Guzzle). symfony/http-client is added to require-dev.
  • CI adds PHP 8.5, and one PHP 8.5 + Guzzle 8 job runs the full suite through the Symfony PSR-18 client.
  • Tests call ReflectionMethod::setAccessible() only before PHP 8.1; PHP 8.5 deprecates it, which failed the run.
  • Docs: docs/use-custom-http-client.md, examples/use-custom-http-client.php, and a README section.

What does this PR address?

Are tests included?

  • Yes
  • No

Reviewer, please note:

  • With a custom client, timeout, connection_timeout, and api_proxy are configured on the client, not in Cloudinary configuration.
  • PSR-18 is sync-only, so *Async() calls made through a custom client run one at a time.
  • Every request carries an Authorization header with the API secret. The docs tell users to redact it if their client logs headers.
  • Drop Guzzle requirement and replace with any PSR-18 HttpClient #396 also asks to drop the Guzzle requirement. This PR keeps Guzzle: requests are still built by Guzzle and *Async() returns Guzzle promises.

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.

Let applications send SDK requests through their own PSR-18 client, so
their logging, retries, proxy and timeouts apply.

- Cloudinary::setHttpClient() sets the client for API objects returned
  by uploadApi(), adminApi(), searchApi() and searchFoldersApi().
- setHttpClient() on UploadApi, AdminApi, SearchApi, SearchFoldersApi
  and AccountApi sets it for objects created directly.
- Cloudinary::setDefaultHttpClient() sets a process-wide default.
- A Guzzle handler (Psr18Handler) forwards each built request to the
  client and maps PSR-18 network and client errors to Guzzle exceptions,
  so the public API, including *Async() return types, is unchanged.
- With a custom client, timeout, connection_timeout and api_proxy are
  owned by the client, and async calls run sequentially.
- Require psr/http-client; add symfony/http-client to require-dev.
- CI: add PHP 8.5; run the suite through the Symfony PSR-18 client on
  PHP 8.5 with Guzzle 8.
- Tests: unit tests for the handler and setters; live scenario tests
  through Guzzle and Symfony PSR-18 clients; CLD_TEST_HTTP_CLIENT runs
  integration tests through a PSR-18 client.
- Tests: call ReflectionMethod::setAccessible() only before PHP 8.1, as
  PHP 8.5 deprecates it.
- Docs: docs/use-custom-http-client.md, an example, and a README section.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@const-cloudinary
const-cloudinary merged commit 05f8589 into master Oct 19, 2026
10 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.

1 participant