PHPackages                             simsoft/http-client - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [HTTP &amp; Networking](/categories/http)
4. /
5. simsoft/http-client

ActiveLibrary[HTTP &amp; Networking](/categories/http)

simsoft/http-client
===================

A fluent, zero-dependency PHP HTTP client with built-in OAuth2, retry, middleware, and streaming — powered by cURL.

2.2.4(1w ago)0143MITPHPPHP ^8.1CI passing

Since Jun 23Pushed 1mo agoCompare

[ Source](https://github.com/sim-soft/http-client)[ Packagist](https://packagist.org/packages/simsoft/http-client)[ RSS](/packages/simsoft-http-client/feed)WikiDiscussions master Synced today

READMEChangelog (10)Dependencies (29)Versions (15)Used By (0)

Simsoft HttpClient
==================

[](#simsoft-httpclient)

A fluent PHP HTTP client built on `ext-curl` with zero runtime dependencies. PSR-7/PSR-18 compliant, concurrent requests, built-in retry, middleware, and test doubles — all in a single lightweight package.

```
$response = HttpClient::make()
    ->withBaseUrl('https://api.example.com')
    ->withBearerToken('YOUR_TOKEN')
    ->get('/users', ['page' => 1]);

echo $response->data('data.0.name'); // "John Doe"
```

Requirements
------------

[](#requirements)

- PHP 8.1+
- ext-curl

Install
-------

[](#install)

```
composer require simsoft/http-client
```

Documentation
-------------

[](#documentation)

Full documentation is available at [sim-soft.github.io/http-client](https://sim-soft.github.io/http-client/).

---

Table of Contents
-----------------

[](#table-of-contents)

### Getting Started

[](#getting-started)

- [Quick Start](#quick-start)
- [Sending Requests](#sending-requests)
- [Request Bodies](#request-bodies)

### Configuration

[](#configuration)

- [Headers](#headers)
- [Timeouts &amp; cURL Options](#timeouts--curl-options)
- [Authentication](#authentication)

### Responses

[](#responses)

- [Status Checks](#status-checks)
- [Reading Data (Dot-notation)](#reading-data)
- [Response Body (Stream)](#response-body)

### File Transfer

[](#file-transfer)

- [Uploading Files](#uploading-files)
- [Downloading Files](#downloading-files)

### Resilience

[](#resilience)

- [Retry](#retry)
- [Middleware](docs/MIDDLEWARE.md)

### Advanced

[](#advanced)

- [Concurrent Requests (HttpPool)](docs/POOL.md)
- [OAuth2 Authentication](docs/OAUTH2.md)
- [PSR-18 Interoperability](docs/PSR18.md)
- [Custom SDK / Response Classes](docs/CUSTOM_SDK.md)
- [Macro &amp; Mixin](docs/MACRO.md)
- [Testing with FakeHttpClient](docs/TESTING.md)
- [Logging](#logging)
- [Debugging](#debugging)

### Reference

[](#reference)

- [Comparison with Other Libraries](docs/COMPARISON.md)

---

Quick Start
-----------

[](#quick-start)

```
use Simsoft\HttpClient\HttpClient;

$client = HttpClient::make()->withBaseUrl('https://api.example.com');

// GET with query params
$response = $client->get('/users', ['page' => 1, 'limit' => 10]);

// Check status and read JSON
if ($response->ok()) {
    $users = $response->data('data');          // array of users
    $names = $response->data('data.*.name');   // ["John", "Jane"]
}
```

Sending Requests
----------------

[](#sending-requests)

```
$client = HttpClient::make()->withBaseUrl('https://api.example.com');

$response = $client->get('/users');
$response = $client->get('/users', ['status' => 'active']);

$response = $client->post('/users', ['name' => 'Alice']);
$response = $client->put('/users/1', ['name' => 'Bob']);
$response = $client->patch('/users/1', ['email' => 'bob@example.com']);
$response = $client->delete('/users/1');
```

Request Bodies
--------------

[](#request-bodies)

```
$client = HttpClient::make()->withBaseUrl('https://api.example.com');

// JSON (application/json)
$client->withJson(['name' => 'Alice'])->post('/users');
$client->asJson()->post('/users', ['name' => 'Alice']);  // shorthand

// Form URL-encoded (application/x-www-form-urlencoded)
$client->withForm(['email' => 'a@b.com'])->post('/login');
$client->asForm()->post('/login', ['email' => 'a@b.com']);

// Multipart form-data
$client->withMultipart(['field' => 'value'])->post('/upload');
$client->post('/upload', ['field' => 'value']);  // default for POST arrays

// Raw body
$client->withRaw('data', 'application/xml')->post('/endpoint');

// Stream body (client takes ownership, closes after request)
$client->withBodyStream(new MyStream(), 'application/pdf')->post('/upload');

// GraphQL
$client->withGraphQL('query { users { name } }', ['limit' => 10])->post('/graphql');
```

Headers
-------

[](#headers)

```
$response = HttpClient::make()
    ->withBaseUrl('https://api.example.com')
    ->withHeader('X-Custom', 'value')
    ->withHeaders([
        'Accept' => 'application/json',
        'X-App-Version' => '2.0',
    ])
    ->get('/data');
```

Timeouts &amp; cURL Options
---------------------------

[](#timeouts--curl-options)

```
$response = HttpClient::make()
    ->timeout(30)              // execution timeout (seconds)
    ->connectionTimeout(5)     // connection timeout (seconds)
    ->withoutVerifying()       // disable TLS verification (dev only)
    ->verbose()                // enable cURL verbose output
    ->withOptions([            // any cURL constant
        CURLOPT_MAXREDIRS => 3,
    ])
    ->get('https://api.example.com/data');
```

Authentication
--------------

[](#authentication)

```
// Bearer token
$client = HttpClient::make()->withBearerToken('YOUR_TOKEN');

// For OAuth2 flows, see docs/OAUTH2.md
```

---

Status Checks
-------------

[](#status-checks)

```
$response->ok();              // 200
$response->created();         // 201
$response->noContent();       // 204
$response->successful();      // 2xx

$response->badRequest();      // 400
$response->unauthorized();    // 401
$response->forbidden();       // 403
$response->notFound();        // 404
$response->tooManyRequests(); // 429
$response->isClientError();   // 4xx

$response->isServerError();   // 5xx
$response->isNetworkError();  // cURL error (timeout, DNS, etc.)
$response->failed();          // 4xx or 5xx or network error

$response->getStatusCode();   // int
$response->getMessage();      // reason phrase or cURL error
$response->getTotalTime();    // float (seconds)
```

Reading Data
------------

[](#reading-data)

Access JSON response data using dot-notation with wildcard support:

```
// Given: {"status": 200, "data": [{"name": "John"}, {"name": "Jane"}]}

$response->data();                // full decoded array
$response->data('status');        // 200
$response->data('data.0.name');   // "John"
$response->data('data.*.name');   // ["John", "Jane"]
$response->data('missing', 'default'); // "default"

$response->json();    // decoded array (same as data())
$response->object();  // decoded as stdClass
$response->toArray(); // decoded array
```

Headers:

```
$response->getHeaders();                  // all headers
$response->getHeaderLine('Content-Type'); // "application/json"
$response->hasHeader('X-Request-Id');     // bool
```

Response Body
-------------

[](#response-body)

The body implements `Psr\Http\Message\StreamInterface`:

```
// Quick access
$raw = $response->body();       // string
$raw = $response->getRaw();     // same
$raw = (string) $response->getBody();

// Stream operations
$body = $response->getBody();
$body->getSize();
$body->getContents();
$body->rewind();

// Chunked reading
while (!$body->eof()) {
    echo $body->read(8192);
}
```

---

Uploading Files
---------------

[](#uploading-files)

Single file:

```
$client = HttpClient::make()->withBaseUrl('https://api.example.com');

// CURLFile (recommended)
$client->attach('file', new CURLFile('path/to/doc.pdf'))->post('/upload');

// From path with custom name and MIME
$client->attach('doc', 'path/to/doc.pdf', 'report.pdf', 'application/pdf')->post('/upload');

// From resource
$client->attach('file', fopen('path/to/doc.pdf', 'r'), 'doc.pdf')->post('/upload');

// From string content
$client->attach('file', 'file content here', 'note.txt', 'text/plain')->post('/upload');
```

Multiple files:

```
$client->attach('files', [
    new CURLFile('path/to/file1.pdf'),
    new CURLFile('path/to/file2.pdf'),
])->post('/upload');
```

Downloading Files
-----------------

[](#downloading-files)

```
// Direct to file (CURLOPT_FILE)
HttpClient::make()->sink('path/to/output.zip')->get('https://example.com/file.zip');

// Stream-based (CURLOPT_WRITEFUNCTION) — for progress tracking or piping
$fp = fopen('path/to/output.zip', 'wb');
HttpClient::make()->sinkStream($fp)->get('https://example.com/file.zip');
fclose($fp);
```

---

Retry
-----

[](#retry)

```
// Retry 3 times with no delay
$response = HttpClient::make()->retry(3)->get('https://api.example.com/data');

// Retry 3 times, 500ms between attempts
$response = HttpClient::make()->retry(3, after: 500)->get('https://api.example.com/data');
```

Custom retry conditions with `retryWhen()`:

```
use Simsoft\HttpClient\Response;

$response = HttpClient::make()
    ->retry(4)
    ->retryWhen(function (Response $response, string $method, int $attempt): bool {
        // Retry on 429 with Retry-After header
        if ($response->getStatusCode() === 429) {
            $wait = (int) $response->getHeaderLine('retry-after');
            sleep(max(1, $wait));
            return true;
        }
        return $response->isRetryableNetworkError();
    })
    ->get('https://api.example.com/search');
```

Exponential backoff:

```
HttpClient::make()
    ->retry(5)
    ->retryWhen(function (Response $response, string $method, int $attempt): bool {
        if (!$response->isServerError() && !$response->isRetryableNetworkError()) {
            return false;
        }
        // 100ms, 200ms, 400ms, 800ms... with ±20% jitter
        $delay = (int) (100 * (2 ** ($attempt - 1)));
        $jitter = (int) ($delay * 0.2);
        usleep(($delay + random_int(-$jitter, $jitter)) * 1000);
        return true;
    })
    ->get('https://api.example.com/reports');
```

Logging
-------

[](#logging)

```
use Monolog\Logger;

$response = HttpClient::make()
    ->withLogger(new Logger('http'))  // any PSR-3 LoggerInterface
    ->get('https://api.example.com/data');
```

Logs method, URL, status, duration, and errno for every request. Errors are logged at `error` level automatically.

Debugging
---------

[](#debugging)

```
// dump() — prints request state, then continues execution
$response = HttpClient::make()->dump()->post('https://api.example.com/data', ['foo' => 'bar']);

// dd() — prints request state and exits immediately
HttpClient::make()->dd()->post('https://api.example.com/data', ['foo' => 'bar']);
```

---

Advanced Topics
---------------

[](#advanced-topics)

TopicDescription[Concurrent Requests](docs/POOL.md)Execute requests in parallel with HttpPool, sliding window, retries, and callbacks[OAuth2](docs/OAUTH2.md)Client credentials, authorization code with PKCE, token caching and refresh[PSR-18](docs/PSR18.md)Use as a drop-in PSR-18 client with any PSR-17 factory[Custom SDK](docs/CUSTOM_SDK.md)Build typed SDK clients and response classes[Macro &amp; Mixin](docs/MACRO.md)Add methods at runtime without subclassing[Middleware](docs/MIDDLEWARE.md)Auth injection, caching, circuit breaking, logging, error normalization[Testing](docs/TESTING.md)FakeHttpClient with pattern matching, sequencing, and PHPUnit assertionsLicense
-------

[](#license)

MIT — see [LICENSE](LICENSE)

###  Health Score

44

—

FairBetter than 90% of packages

Maintenance95

Actively maintained with recent releases

Popularity11

Limited adoption so far

Community8

Small or concentrated contributor base

Maturity53

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 62.9% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~36 days

Recently: every ~12 days

Total

11

Last Release

9d ago

Major Versions

1.0.3 → 2.0.02026-04-24

PHP version history (2 changes)1.0.0PHP ^8

2.2.1PHP ^8.1

### Community

Maintainers

![](https://www.gravatar.com/avatar/7c3e6315469b56ed1797318e31e05bcddb12dba268488a2fb0cd2b43971c9ac3?d=identicon)[vzangloo](/maintainers/vzangloo)

---

Top Contributors

[![vzangloo](https://avatars.githubusercontent.com/u/1908200?v=4)](https://github.com/vzangloo "vzangloo (22 commits)")[![sim-soft](https://avatars.githubusercontent.com/u/118705222?v=4)](https://github.com/sim-soft "sim-soft (13 commits)")

---

Tags

psr-7middlewarerestcurlhttp clientpsr-18streamingoauth2retryapi clientfluent-apipkce

###  Code Quality

TestsPHPUnit

Static AnalysisPHPStan

Code StylePHP\_CodeSniffer

Type Coverage Yes

### Embed Badge

![Health badge](/badges/simsoft-http-client/health.svg)

```
[![Health](https://phpackages.com/badges/simsoft-http-client/health.svg)](https://phpackages.com/packages/simsoft-http-client)
```

###  Alternatives

[tempest/framework

The PHP framework that gets out of your way.

2.2k34.4k15](/packages/tempest-framework)[guzzlehttp/psr7

PSR-7 message implementation that also provides common utility methods

8.0k1.1B4.0k](/packages/guzzlehttp-psr7)[guzzlehttp/guzzle

Guzzle is a PHP HTTP client library

23.5k1.0B35.4k](/packages/guzzlehttp-guzzle)[flow-php/flow

PHP ETL - Extract Transform Load - Data processing framework

85036.3k](/packages/flow-php-flow)[telnyx/telnyx-php

Official Telnyx PHP SDK — APIs for Voice, SMS, MMS, WhatsApp, Fax, SIP Trunking, Wireless IoT, Call Control, and more. Build global communications on Telnyx's private carrier-grade network.

35789.4k2](/packages/telnyx-telnyx-php)[laudis/neo4j-php-client

Neo4j-PHP-Client is the most advanced PHP Client for Neo4j

185702.8k43](/packages/laudis-neo4j-php-client)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
